James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
b53611 1 /*
JM 2  * Copyright 2014 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.gitblit.transport.ssh;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.List;
aaecd8 23 import java.util.Map;
JM 24 import java.util.concurrent.ConcurrentHashMap;
b53611 25
c78b25 26 import com.gitblit.Constants.AccessPermission;
b53611 27 import com.gitblit.Keys;
JM 28 import com.gitblit.manager.IRuntimeManager;
29 import com.google.common.base.Charsets;
8384e0 30 import com.google.common.base.Joiner;
b53611 31 import com.google.common.io.Files;
241f57 32 import com.google.inject.Inject;
b53611 33
JM 34 /**
245836 35  * Manages public keys on the filesystem.
8384e0 36  *
b53611 37  * @author James Moger
JM 38  *
39  */
245836 40 public class FileKeyManager extends IPublicKeyManager {
b53611 41
JM 42     protected final IRuntimeManager runtimeManager;
8384e0 43
aaecd8 44     protected final Map<File, Long> lastModifieds;
JM 45
241f57 46     @Inject
b53611 47     public FileKeyManager(IRuntimeManager runtimeManager) {
JM 48         this.runtimeManager = runtimeManager;
aaecd8 49         this.lastModifieds = new ConcurrentHashMap<File, Long>();
b53611 50     }
8384e0 51
b53611 52     @Override
JM 53     public String toString() {
54         File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
55         return MessageFormat.format("{0} ({1})", getClass().getSimpleName(), dir);
56     }
8384e0 57
b53611 58     @Override
JM 59     public FileKeyManager start() {
245836 60         log.info(toString());
b53611 61         return this;
JM 62     }
8384e0 63
b53611 64     @Override
JM 65     public boolean isReady() {
66         return true;
67     }
8384e0 68
b53611 69     @Override
JM 70     public FileKeyManager stop() {
71         return this;
72     }
73
74     @Override
aaecd8 75     protected boolean isStale(String username) {
JM 76         File keystore = getKeystore(username);
77         if (!keystore.exists()) {
78             // keystore may have been deleted
79             return true;
80         }
81
82         if (lastModifieds.containsKey(keystore)) {
83             // compare modification times
84             long lastModified = lastModifieds.get(keystore);
85             return lastModified != keystore.lastModified();
86         }
87
88         // assume stale
89         return true;
90     }
91
92     @Override
bcc8a0 93     protected List<SshKey> getKeysImpl(String username) {
b53611 94         try {
521cb6 95             log.info("loading ssh keystore for {}", username);
aaecd8 96             File keystore = getKeystore(username);
JM 97             if (!keystore.exists()) {
b53611 98                 return null;
JM 99             }
aaecd8 100             if (keystore.exists()) {
bcc8a0 101                 List<SshKey> list = new ArrayList<SshKey>();
aaecd8 102                 for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
b53611 103                     if (entry.trim().length() == 0) {
JM 104                         // skip blanks
105                         continue;
106                     }
107                     if (entry.charAt(0) == '#') {
108                         // skip comments
109                         continue;
110                     }
c78b25 111                     String [] parts = entry.split(" ", 2);
JM 112                     AccessPermission perm = AccessPermission.fromCode(parts[0]);
113                     if (perm.equals(AccessPermission.NONE)) {
114                         // ssh-rsa DATA COMMENT
115                         SshKey key = new SshKey(entry);
116                         list.add(key);
117                     } else if (perm.exceeds(AccessPermission.NONE)) {
118                         // PERMISSION ssh-rsa DATA COMMENT
119                         SshKey key = new SshKey(parts[1]);
120                         key.setPermission(perm);
121                         list.add(key);
122                     }
b53611 123                 }
8384e0 124
b53611 125                 if (list.isEmpty()) {
JM 126                     return null;
127                 }
aaecd8 128
JM 129                 lastModifieds.put(keystore, keystore.lastModified());
b53611 130                 return list;
JM 131             }
132         } catch (IOException e) {
521cb6 133             throw new RuntimeException("Cannot read ssh keys", e);
b53611 134         }
JM 135         return null;
136     }
137
8384e0 138     /**
JM 139      * Adds a unique key to the keystore.  This function determines uniqueness
140      * by disregarding the comment/description field during key comparisons.
141      */
b53611 142     @Override
bcc8a0 143     public boolean addKey(String username, SshKey key) {
b53611 144         try {
ab07d0 145             boolean replaced = false;
8384e0 146             List<String> lines = new ArrayList<String>();
JM 147             File keystore = getKeystore(username);
148             if (keystore.exists()) {
149                 for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
150                     String line = entry.trim();
151                     if (line.length() == 0) {
152                         // keep blanks
153                         lines.add(entry);
154                         continue;
155                     }
156                     if (line.charAt(0) == '#') {
157                         // keep comments
158                         lines.add(entry);
159                         continue;
160                     }
161
c78b25 162                     SshKey oldKey = parseKey(line);
JM 163                     if (key.equals(oldKey)) {
ab07d0 164                         // replace key
c78b25 165                         lines.add(key.getPermission() + " " + key.getRawData());
ab07d0 166                         replaced = true;
JM 167                     } else {
168                         // retain key
8384e0 169                         lines.add(entry);
JM 170                     }
171                 }
172             }
173
ab07d0 174             if (!replaced) {
JM 175                 // new key, append
c78b25 176                 lines.add(key.getPermission() + " " + key.getRawData());
ab07d0 177             }
8384e0 178
JM 179             // write keystore
180             String content = Joiner.on("\n").join(lines).trim().concat("\n");
181             Files.write(content, keystore, Charsets.ISO_8859_1);
aaecd8 182
JM 183             lastModifieds.remove(keystore);
184             keyCache.invalidate(username);
b53611 185             return true;
JM 186         } catch (IOException e) {
187             throw new RuntimeException("Cannot add ssh key", e);
188         }
189     }
8384e0 190
JM 191     /**
bcc8a0 192      * Removes the specified key from the keystore.
8384e0 193      */
b53611 194     @Override
bcc8a0 195     public boolean removeKey(String username, SshKey key) {
b53611 196         try {
JM 197             File keystore = getKeystore(username);
198             if (keystore.exists()) {
8384e0 199                 List<String> lines = new ArrayList<String>();
JM 200                 for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
201                     String line = entry.trim();
202                     if (line.length() == 0) {
b53611 203                         // keep blanks
8384e0 204                         lines.add(entry);
b53611 205                         continue;
JM 206                     }
8384e0 207                     if (line.charAt(0) == '#') {
b53611 208                         // keep comments
8384e0 209                         lines.add(entry);
b53611 210                         continue;
JM 211                     }
8384e0 212
JM 213                     // only include keys that are NOT rmKey
c78b25 214                     SshKey oldKey = parseKey(line);
JM 215                     if (!key.equals(oldKey)) {
8384e0 216                         lines.add(entry);
b53611 217                     }
8384e0 218                 }
JM 219                 if (lines.isEmpty()) {
220                     keystore.delete();
221                 } else {
222                     // write keystore
223                     String content = Joiner.on("\n").join(lines).trim().concat("\n");
224                     Files.write(content, keystore, Charsets.ISO_8859_1);
b53611 225                 }
aaecd8 226
JM 227                 lastModifieds.remove(keystore);
228                 keyCache.invalidate(username);
b53611 229                 return true;
JM 230             }
231         } catch (IOException e) {
232             throw new RuntimeException("Cannot remove ssh key", e);
233         }
234         return false;
235     }
5eafd9 236
DO 237     @Override
8ee7c8 238     public boolean removeAllKeys(String username) {
aaecd8 239         File keystore = getKeystore(username);
JM 240         if (keystore.delete()) {
241             lastModifieds.remove(keystore);
242             keyCache.invalidate(username);
243             return true;
244         }
245         return false;
5eafd9 246     }
DO 247
b53611 248     protected File getKeystore(String username) {
JM 249         File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
250         dir.mkdirs();
251         File keys = new File(dir, username + ".keys");
252         return keys;
253     }
8384e0 254
c78b25 255     protected SshKey parseKey(String line) {
JM 256         String [] parts = line.split(" ", 2);
257         AccessPermission perm = AccessPermission.fromCode(parts[0]);
258         if (perm.equals(AccessPermission.NONE)) {
259             // ssh-rsa DATA COMMENT
260             SshKey key = new SshKey(line);
261             return key;
262         } else {
263             // PERMISSION ssh-rsa DATA COMMENT
264             SshKey key = new SshKey(parts[1]);
265             key.setPermission(perm);
266             return key;
267         }
8384e0 268     }
b53611 269 }