Line data Source code
1 : // Copyright (C) 2016 The Android Open Source Project 2 : // Licensed under the Apache License, Version 2.0 (the "License"); 3 : // you may not use this file except in compliance with the License. 4 : // You may obtain a copy of the License at 5 : // 6 : // http://www.apache.org/licenses/LICENSE-2.0 7 : // 8 : // Unless required by applicable law or agreed to in writing, software 9 : // distributed under the License is distributed on an "AS IS" BASIS, 10 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 : // See the License for the specific language governing permissions and 12 : // limitations under the License. 13 : 14 : package com.google.gerrit.server.query.account; 15 : 16 : import com.google.common.base.Splitter; 17 : import com.google.common.collect.ImmutableList; 18 : import com.google.common.collect.ImmutableSet; 19 : import com.google.common.collect.Lists; 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.common.primitives.Ints; 22 : import com.google.gerrit.common.Nullable; 23 : import com.google.gerrit.entities.Account; 24 : import com.google.gerrit.exceptions.NotSignedInException; 25 : import com.google.gerrit.exceptions.StorageException; 26 : import com.google.gerrit.index.Index; 27 : import com.google.gerrit.index.Schema; 28 : import com.google.gerrit.index.query.LimitPredicate; 29 : import com.google.gerrit.index.query.Predicate; 30 : import com.google.gerrit.index.query.QueryBuilder; 31 : import com.google.gerrit.index.query.QueryParseException; 32 : import com.google.gerrit.server.CurrentUser; 33 : import com.google.gerrit.server.IdentifiedUser; 34 : import com.google.gerrit.server.account.AccountState; 35 : import com.google.gerrit.server.change.ChangeFinder; 36 : import com.google.gerrit.server.index.account.AccountField; 37 : import com.google.gerrit.server.index.account.AccountIndexCollection; 38 : import com.google.gerrit.server.notedb.ChangeNotes; 39 : import com.google.gerrit.server.permissions.ChangePermission; 40 : import com.google.gerrit.server.permissions.GlobalPermission; 41 : import com.google.gerrit.server.permissions.PermissionBackend; 42 : import com.google.gerrit.server.permissions.PermissionBackendException; 43 : import com.google.gerrit.server.query.account.AccountPredicates.AccountPredicate; 44 : import com.google.gerrit.server.query.change.ChangeData; 45 : import com.google.inject.Inject; 46 : import com.google.inject.Provider; 47 : import com.google.inject.ProvisionException; 48 : import java.util.Optional; 49 : 50 : /** Parses a query string meant to be applied to account objects. */ 51 : public class AccountQueryBuilder extends QueryBuilder<AccountState, AccountQueryBuilder> { 52 92 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 53 : 54 : public static final String FIELD_ACCOUNT = "account"; 55 : public static final String FIELD_CAN_SEE = "cansee"; 56 : public static final String FIELD_EMAIL = "email"; 57 : public static final String FIELD_LIMIT = "limit"; 58 : public static final String FIELD_NAME = "name"; 59 : public static final String FIELD_PREFERRED_EMAIL = "preferredemail"; 60 : public static final String FIELD_PREFERRED_EMAIL_EXACT = "preferredemail_exact"; 61 : public static final String FIELD_USERNAME = "username"; 62 : public static final String FIELD_VISIBLETO = "visibleto"; 63 : 64 92 : private static final QueryBuilder.Definition<AccountState, AccountQueryBuilder> mydef = 65 : new QueryBuilder.Definition<>(AccountQueryBuilder.class); 66 : 67 : public static class Arguments { 68 : final ChangeFinder changeFinder; 69 : final ChangeData.Factory changeDataFactory; 70 : final PermissionBackend permissionBackend; 71 : 72 : private final Provider<CurrentUser> self; 73 : private final AccountIndexCollection indexes; 74 : 75 : @Inject 76 : public Arguments( 77 : Provider<CurrentUser> self, 78 : AccountIndexCollection indexes, 79 : ChangeFinder changeFinder, 80 : ChangeData.Factory changeDataFactory, 81 92 : PermissionBackend permissionBackend) { 82 92 : this.self = self; 83 92 : this.indexes = indexes; 84 92 : this.changeDataFactory = changeDataFactory; 85 92 : this.changeFinder = changeFinder; 86 92 : this.permissionBackend = permissionBackend; 87 92 : } 88 : 89 : IdentifiedUser getIdentifiedUser() throws QueryParseException { 90 : try { 91 2 : CurrentUser u = getUser(); 92 2 : if (u.isIdentifiedUser()) { 93 2 : return u.asIdentifiedUser(); 94 : } 95 2 : throw new QueryParseException(NotSignedInException.MESSAGE); 96 0 : } catch (ProvisionException e) { 97 0 : throw new QueryParseException(NotSignedInException.MESSAGE, e); 98 : } 99 : } 100 : 101 : CurrentUser getUser() throws QueryParseException { 102 : try { 103 6 : return self.get(); 104 0 : } catch (ProvisionException e) { 105 0 : throw new QueryParseException(NotSignedInException.MESSAGE, e); 106 : } 107 : } 108 : 109 : @Nullable 110 : Schema<AccountState> schema() { 111 6 : Index<?, AccountState> index = indexes != null ? indexes.getSearchIndex() : null; 112 6 : return index != null ? index.getSchema() : null; 113 : } 114 : } 115 : 116 : private final Arguments args; 117 : 118 : @Inject 119 : AccountQueryBuilder(Arguments args) { 120 92 : super(mydef, null); 121 92 : this.args = args; 122 92 : } 123 : 124 : @Operator 125 : public Predicate<AccountState> cansee(String change) 126 : throws QueryParseException, PermissionBackendException { 127 2 : Optional<ChangeNotes> changeNotes = args.changeFinder.findOne(change); 128 2 : if (!changeNotes.isPresent()) { 129 0 : throw error(String.format("change %s not found", change)); 130 : } 131 2 : if (changeNotes.get().getChange().isPrivate()) { 132 2 : Account.Id caller = self(); 133 2 : ChangeData cd = args.changeDataFactory.create(changeNotes.get()); 134 2 : Account.Id owner = cd.change().getOwner(); 135 2 : ImmutableSet<Account.Id> reviewersAndCC = cd.reviewers().all(); 136 2 : if (!(caller.equals(owner) || reviewersAndCC.contains(caller))) { 137 2 : throw error(String.format("change %s not found", change)); 138 : } 139 2 : return orAccountPredicate( 140 2 : ImmutableList.<Account.Id>builder().add(owner).addAll(reviewersAndCC).build()); 141 : } 142 2 : if (!args.permissionBackend 143 2 : .user(args.getUser()) 144 2 : .change(changeNotes.get()) 145 2 : .test(ChangePermission.READ)) { 146 0 : throw error(String.format("change %s not found", change)); 147 : } 148 2 : return AccountPredicates.cansee(args, changeNotes.get()); 149 : } 150 : 151 : @Operator 152 : public Predicate<AccountState> email(String email) 153 : throws PermissionBackendException, QueryParseException { 154 2 : if (canSeeSecondaryEmails()) { 155 2 : return AccountPredicates.emailIncludingSecondaryEmails(email); 156 : } 157 : 158 2 : if (args.schema().hasField(AccountField.PREFERRED_EMAIL_LOWER_CASE_SPEC)) { 159 2 : return AccountPredicates.preferredEmail(email); 160 : } 161 : 162 0 : throw new QueryParseException("'email' operator is not supported by account index version"); 163 : } 164 : 165 : @Operator 166 : public Predicate<AccountState> is(String value) throws QueryParseException { 167 3 : if ("active".equalsIgnoreCase(value)) { 168 3 : return AccountPredicates.isActive(); 169 : } 170 2 : if ("inactive".equalsIgnoreCase(value)) { 171 2 : return AccountPredicates.isNotActive(); 172 : } 173 0 : throw error("Invalid query"); 174 : } 175 : 176 : @Operator 177 : public Predicate<AccountState> limit(String query) throws QueryParseException { 178 0 : Integer limit = Ints.tryParse(query); 179 0 : if (limit == null) { 180 0 : throw error("Invalid limit: " + query); 181 : } 182 0 : return new LimitPredicate<>(FIELD_LIMIT, limit); 183 : } 184 : 185 : @Operator 186 : public Predicate<AccountState> name(String name) 187 : throws PermissionBackendException, QueryParseException { 188 2 : if (canSeeSecondaryEmails()) { 189 2 : return AccountPredicates.equalsNameIncludingSecondaryEmails(name); 190 : } 191 : 192 2 : if (args.schema().hasField(AccountField.NAME_PART_NO_SECONDARY_EMAIL_SPEC)) { 193 2 : return AccountPredicates.equalsName(name); 194 : } 195 : 196 0 : return AccountPredicates.fullName(name); 197 : } 198 : 199 : @Operator 200 : public Predicate<AccountState> username(String username) { 201 2 : return AccountPredicates.username(username); 202 : } 203 : 204 : public Predicate<AccountState> defaultQuery(String query) { 205 4 : return Predicate.and( 206 4 : Lists.transform( 207 4 : Splitter.on(' ').omitEmptyStrings().splitToList(query), this::defaultField)); 208 : } 209 : 210 : @Override 211 : protected Predicate<AccountState> defaultField(String query) { 212 6 : Predicate<AccountState> defaultPredicate = 213 6 : AccountPredicates.defaultPredicate(args.schema(), checkedCanSeeSecondaryEmails(), query); 214 6 : if (query.startsWith("cansee:")) { 215 : try { 216 0 : return cansee(query.substring(7)); 217 0 : } catch (StorageException | QueryParseException | PermissionBackendException e) { 218 : // Ignore, fall back to default query 219 : } 220 : } 221 : 222 6 : if ("self".equalsIgnoreCase(query) || "me".equalsIgnoreCase(query)) { 223 : try { 224 2 : return Predicate.or(defaultPredicate, AccountPredicates.id(args.schema(), self())); 225 2 : } catch (QueryParseException e) { 226 : // Skip. 227 : } 228 : } 229 6 : return defaultPredicate; 230 : } 231 : 232 : private Account.Id self() throws QueryParseException { 233 2 : return args.getIdentifiedUser().getAccountId(); 234 : } 235 : 236 : private boolean canSeeSecondaryEmails() throws PermissionBackendException, QueryParseException { 237 6 : return args.permissionBackend.user(args.getUser()).test(GlobalPermission.MODIFY_ACCOUNT); 238 : } 239 : 240 : private boolean checkedCanSeeSecondaryEmails() { 241 : try { 242 6 : return canSeeSecondaryEmails(); 243 0 : } catch (PermissionBackendException e) { 244 0 : logger.atSevere().withCause(e).log("Permission check failed"); 245 0 : return false; 246 0 : } catch (QueryParseException e) { 247 : // User is not signed in. 248 0 : return false; 249 : } 250 : } 251 : 252 : /** Creates an OR predicate of the account IDs of the {@code accounts} parameter. */ 253 : private Predicate<AccountState> orAccountPredicate(ImmutableList<Account.Id> accounts) { 254 2 : Predicate<AccountState> result = 255 2 : AccountPredicate.or(AccountPredicates.id(args.schema(), accounts.get(0))); 256 2 : for (int i = 1; i < accounts.size(); i += 1) { 257 2 : result = AccountPredicate.or(result, AccountPredicates.id(args.schema(), accounts.get(i))); 258 : } 259 2 : return result; 260 : } 261 : }