Line data Source code
1 : // Copyright (C) 2014 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.pgm.init; 16 : 17 : import static java.nio.charset.StandardCharsets.UTF_8; 18 : 19 : import com.google.common.base.Strings; 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.entities.Account; 22 : import com.google.gerrit.entities.GroupReference; 23 : import com.google.gerrit.entities.InternalGroup; 24 : import com.google.gerrit.exceptions.NoSuchGroupException; 25 : import com.google.gerrit.extensions.client.AuthType; 26 : import com.google.gerrit.pgm.init.api.ConsoleUI; 27 : import com.google.gerrit.pgm.init.api.InitFlags; 28 : import com.google.gerrit.pgm.init.api.InitStep; 29 : import com.google.gerrit.pgm.init.api.SequencesOnInit; 30 : import com.google.gerrit.server.account.AccountSshKey; 31 : import com.google.gerrit.server.account.AccountState; 32 : import com.google.gerrit.server.account.externalids.ExternalId; 33 : import com.google.gerrit.server.account.externalids.ExternalIdFactory; 34 : import com.google.gerrit.server.index.account.AccountIndex; 35 : import com.google.gerrit.server.index.account.AccountIndexCollection; 36 : import com.google.gerrit.server.index.group.GroupIndex; 37 : import com.google.gerrit.server.index.group.GroupIndexCollection; 38 : import com.google.gerrit.server.util.time.TimeUtil; 39 : import com.google.inject.Inject; 40 : import java.io.IOException; 41 : import java.nio.file.Files; 42 : import java.nio.file.Path; 43 : import java.nio.file.Paths; 44 : import java.util.ArrayList; 45 : import java.util.List; 46 : import java.util.Optional; 47 : import org.apache.commons.validator.routines.EmailValidator; 48 : 49 : public class InitAdminUser implements InitStep { 50 : private final InitFlags flags; 51 : private final ConsoleUI ui; 52 : private final AccountsOnInit accounts; 53 : private final VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory; 54 : private final ExternalIdsOnInit externalIds; 55 : private final SequencesOnInit sequencesOnInit; 56 : private final GroupsOnInit groupsOnInit; 57 : private final ExternalIdFactory externalIdFactory; 58 : private AccountIndexCollection accountIndexCollection; 59 : private GroupIndexCollection groupIndexCollection; 60 : 61 : @Inject 62 : InitAdminUser( 63 : InitFlags flags, 64 : ConsoleUI ui, 65 : AccountsOnInit accounts, 66 : VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory, 67 : ExternalIdsOnInit externalIds, 68 : SequencesOnInit sequencesOnInit, 69 : GroupsOnInit groupsOnInit, 70 15 : ExternalIdFactory externalIdFactory) { 71 15 : this.flags = flags; 72 15 : this.ui = ui; 73 15 : this.accounts = accounts; 74 15 : this.authorizedKeysFactory = authorizedKeysFactory; 75 15 : this.externalIds = externalIds; 76 15 : this.sequencesOnInit = sequencesOnInit; 77 15 : this.groupsOnInit = groupsOnInit; 78 15 : this.externalIdFactory = externalIdFactory; 79 15 : } 80 : 81 : @Override 82 15 : public void run() {} 83 : 84 : @Inject 85 : void set(AccountIndexCollection accountIndexCollection) { 86 15 : this.accountIndexCollection = accountIndexCollection; 87 15 : } 88 : 89 : @Inject 90 : void set(GroupIndexCollection groupIndexCollection) { 91 15 : this.groupIndexCollection = groupIndexCollection; 92 15 : } 93 : 94 : @Override 95 : public void postRun() throws Exception { 96 15 : if (!accounts.hasAnyAccount()) { 97 15 : welcome(); 98 : } 99 15 : AuthType authType = flags.cfg.getEnum(AuthType.values(), "auth", null, "type", null); 100 15 : if (authType != AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) { 101 15 : return; 102 : } 103 : 104 0 : if (!accounts.hasAnyAccount()) { 105 0 : ui.header("Gerrit Administrator"); 106 0 : if (ui.yesno(true, "Create administrator user")) { 107 0 : Account.Id id = Account.id(sequencesOnInit.nextAccountId()); 108 0 : String username = ui.readString("admin", "username"); 109 0 : String name = ui.readString("Administrator", "name"); 110 0 : String httpPassword = ui.readString("secret", "HTTP password"); 111 0 : AccountSshKey sshKey = readSshKey(id); 112 0 : String email = readEmail(sshKey); 113 : 114 0 : List<ExternalId> extIds = new ArrayList<>(2); 115 0 : extIds.add(externalIdFactory.createUsername(username, id, httpPassword)); 116 : 117 0 : if (email != null) { 118 0 : extIds.add(externalIdFactory.createEmail(id, email)); 119 : } 120 0 : externalIds.insert("Add external IDs for initial admin user", extIds); 121 : 122 0 : Account persistedAccount = 123 0 : accounts.insert( 124 0 : Account.builder(id, TimeUtil.now()).setFullName(name).setPreferredEmail(email)); 125 : // Only two groups should exist at this point in time and hence iterating over all of them 126 : // is cheap. 127 0 : Optional<GroupReference> adminGroupReference = 128 : groupsOnInit 129 0 : .getAllGroupReferences() 130 0 : .filter(group -> group.getName().equals("Administrators")) 131 0 : .findAny(); 132 0 : if (!adminGroupReference.isPresent()) { 133 0 : throw new NoSuchGroupException("Administrators"); 134 : } 135 0 : GroupReference adminGroup = adminGroupReference.get(); 136 0 : groupsOnInit.addGroupMember(adminGroup.getUUID(), persistedAccount); 137 : 138 0 : if (sshKey != null) { 139 0 : VersionedAuthorizedKeysOnInit authorizedKeys = authorizedKeysFactory.create(id).load(); 140 0 : authorizedKeys.addKey(sshKey.sshPublicKey()); 141 0 : authorizedKeys.save("Add SSH key for initial admin user\n"); 142 : } 143 : 144 0 : AccountState as = AccountState.forAccount(persistedAccount, extIds); 145 0 : for (AccountIndex accountIndex : accountIndexCollection.getWriteIndexes()) { 146 0 : accountIndex.replace(as); 147 0 : } 148 : 149 0 : InternalGroup adminInternalGroup = groupsOnInit.getExistingGroup(adminGroup); 150 0 : for (GroupIndex groupIndex : groupIndexCollection.getWriteIndexes()) { 151 0 : groupIndex.replace(adminInternalGroup); 152 0 : } 153 : } 154 : } 155 0 : } 156 : 157 : private void welcome() { 158 15 : ui.message( 159 : "============================================================================\n" 160 : + "Welcome to the Gerrit community\n\n" 161 : + "Find more information on the homepage: https://www.gerritcodereview.com\n" 162 : + "Discuss Gerrit on the mailing list: https://groups.google.com/g/repo-discuss\n" 163 : + "============================================================================\n"); 164 15 : } 165 : 166 : private String readEmail(AccountSshKey sshKey) { 167 0 : String defaultEmail = "admin@example.com"; 168 0 : if (sshKey != null && sshKey.comment() != null) { 169 0 : String c = sshKey.comment().trim(); 170 0 : if (EmailValidator.getInstance().isValid(c)) { 171 0 : defaultEmail = c; 172 : } 173 : } 174 0 : return readEmail(defaultEmail); 175 : } 176 : 177 : private String readEmail(String defaultEmail) { 178 0 : String email = ui.readString(defaultEmail, "email"); 179 0 : if (email != null && !EmailValidator.getInstance().isValid(email)) { 180 0 : ui.message("error: invalid email address\n"); 181 0 : return readEmail(defaultEmail); 182 : } 183 0 : return email; 184 : } 185 : 186 : @Nullable 187 : private AccountSshKey readSshKey(Account.Id id) throws IOException { 188 0 : String defaultPublicSshKeyFile = ""; 189 0 : Path defaultPublicSshKeyPath = Paths.get(System.getProperty("user.home"), ".ssh", "id_rsa.pub"); 190 0 : if (Files.exists(defaultPublicSshKeyPath)) { 191 0 : defaultPublicSshKeyFile = defaultPublicSshKeyPath.toString(); 192 : } 193 0 : String publicSshKeyFile = ui.readString(defaultPublicSshKeyFile, "public SSH key file"); 194 0 : return !Strings.isNullOrEmpty(publicSshKeyFile) ? createSshKey(id, publicSshKeyFile) : null; 195 : } 196 : 197 : private AccountSshKey createSshKey(Account.Id id, String keyFile) throws IOException { 198 0 : Path p = Paths.get(keyFile); 199 0 : if (!Files.exists(p)) { 200 0 : throw new IOException(String.format("Cannot add public SSH key: %s is not a file", keyFile)); 201 : } 202 0 : String content = new String(Files.readAllBytes(p), UTF_8); 203 0 : return AccountSshKey.create(id, 1, content); 204 : } 205 : }