// Copyright (C) 2009 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.server.account; import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_MAILTO; import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.gerrit.common.Nullable; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.CurrentUser.PropertyKey; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.WatchConfig.NotifyType; import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.codec.DecoderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AccountState { private static final Logger logger = LoggerFactory.getLogger(AccountState.class); public static final Function ACCOUNT_ID_FUNCTION = a -> a.getAccount().getId(); private final Account account; private final Set internalGroups; private final Collection externalIds; private final Map> projectWatches; private Cache, Object> properties; public AccountState( Account account, Set actualGroups, Collection externalIds, Map> projectWatches) { this.account = account; this.internalGroups = actualGroups; this.externalIds = externalIds; this.projectWatches = projectWatches; this.account.setUserName(getUserName(externalIds)); } /** Get the cached account metadata. */ public Account getAccount() { return account; } /** * Get the username, if one has been declared for this user. * *

The username is the {@link AccountExternalId} using the scheme {@link * AccountExternalId#SCHEME_USERNAME}. */ public String getUserName() { return account.getUserName(); } public boolean checkPassword(String password, String username) { if (password == null) { return false; } for (AccountExternalId id : getExternalIds()) { // Only process the "username:$USER" entry, which is unique. if (!id.isScheme(AccountExternalId.SCHEME_USERNAME) || !username.equals(id.getSchemeRest())) { continue; } String hashedStr = id.getPassword(); if (!Strings.isNullOrEmpty(hashedStr)) { try { return HashedPassword.decode(hashedStr).checkPassword(password); } catch (DecoderException e) { logger.error( String.format("DecoderException for user %s: %s ", username, e.getMessage())); return false; } } } return false; } /** The external identities that identify the account holder. */ public Collection getExternalIds() { return externalIds; } /** The project watches of the account. */ public Map> getProjectWatches() { return projectWatches; } /** The set of groups maintained directly within the Gerrit database. */ public Set getInternalGroups() { return internalGroups; } public static String getUserName(Collection ids) { for (AccountExternalId id : ids) { if (id.isScheme(SCHEME_USERNAME)) { return id.getSchemeRest(); } } return null; } public static Set getEmails(Collection ids) { Set emails = new HashSet<>(); for (AccountExternalId id : ids) { if (id.isScheme(SCHEME_MAILTO)) { emails.add(id.getSchemeRest()); } } return emails; } /** * Lookup a previously stored property. * *

All properties are automatically cleared when the account cache invalidates the {@code * AccountState}. This method is thread-safe. * * @param key unique property key. * @return previously stored value, or {@code null}. */ @Nullable public T get(PropertyKey key) { Cache, Object> p = properties(false); if (p != null) { @SuppressWarnings("unchecked") T value = (T) p.getIfPresent(key); return value; } return null; } /** * Store a property for later retrieval. * *

This method is thread-safe. * * @param key unique property key. * @param value value to store; or {@code null} to clear the value. */ public void put(PropertyKey key, @Nullable T value) { Cache, Object> p = properties(value != null); if (p != null) { @SuppressWarnings("unchecked") PropertyKey k = (PropertyKey) key; if (value != null) { p.put(k, value); } else { p.invalidate(k); } } } private synchronized Cache, Object> properties(boolean allocate) { if (properties == null && allocate) { properties = CacheBuilder.newBuilder() .concurrencyLevel(1) .initialCapacity(16) // Use weakKeys to ensure plugins that garbage collect will also // eventually release data held in any still live AccountState. .weakKeys() .build(); } return properties; } }