View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.mcr.acl.accesskey;
20  
21  import static java.nio.charset.StandardCharsets.UTF_8;
22  
23  import java.security.NoSuchAlgorithmException;
24  import java.util.Date;
25  import java.util.List;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.mycore.access.MCRAccessCacheHelper;
30  import org.mycore.access.MCRAccessManager;
31  import org.mycore.backend.jpa.MCREntityManagerProvider;
32  import org.mycore.common.MCRException;
33  import org.mycore.common.MCRSessionMgr;
34  import org.mycore.common.MCRUtils;
35  import org.mycore.common.config.MCRConfiguration2;
36  import org.mycore.crypt.MCRCipher;
37  import org.mycore.crypt.MCRCipherManager;
38  import org.mycore.crypt.MCRCryptKeyFileNotFoundException;
39  import org.mycore.crypt.MCRCryptKeyNoPermissionException;
40  import org.mycore.datamodel.metadata.MCRObjectID;
41  import org.mycore.mcr.acl.accesskey.exception.MCRAccessKeyCollisionException;
42  import org.mycore.mcr.acl.accesskey.exception.MCRAccessKeyException;
43  import org.mycore.mcr.acl.accesskey.exception.MCRAccessKeyInvalidSecretException;
44  import org.mycore.mcr.acl.accesskey.exception.MCRAccessKeyInvalidTypeException;
45  import org.mycore.mcr.acl.accesskey.exception.MCRAccessKeyNotFoundException;
46  import org.mycore.mcr.acl.accesskey.model.MCRAccessKey;
47  
48  import jakarta.persistence.EntityManager;
49  
50  /**
51   * Methods to manage {@link MCRAccessKey}.
52   */
53  public final class MCRAccessKeyManager {
54  
55      private static final Logger LOGGER = LogManager.getLogger();
56  
57      private static final String SECRET_STORAGE_MODE_PROP_PREFX = "MCR.ACL.AccessKey.Secret.Storage.Mode";
58  
59      private static final String SECRET_STORAGE_MODE = MCRConfiguration2
60          .getStringOrThrow(SECRET_STORAGE_MODE_PROP_PREFX);
61  
62      private static final int HASHING_ITERATIONS = MCRConfiguration2
63          .getInt(SECRET_STORAGE_MODE_PROP_PREFX + ".Hash.Iterations").orElse(1000);
64  
65      /**
66       * Returns all access keys for given {@link MCRObjectID}.
67       *
68       * @param objectId the {@link MCRObjectID}
69       * @return {@link MCRAccessKey} list
70       */
71      public static synchronized List<MCRAccessKey> listAccessKeys(final MCRObjectID objectId) {
72          final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
73          final List<MCRAccessKey> accessKeys = em.createNamedQuery("MCRAccessKey.getWithObjectId", MCRAccessKey.class)
74              .setParameter("objectId", objectId)
75              .getResultList();
76          for (MCRAccessKey accessKey : accessKeys) {
77              em.detach(accessKey);
78          }
79          return accessKeys;
80      }
81  
82      /**
83       * Checks the quality of the permission.
84       *
85       * @param type permission type
86       * @return true if valid or false
87       */
88      public static boolean isValidType(final String type) {
89          return (MCRAccessManager.PERMISSION_READ.equals(type) || MCRAccessManager.PERMISSION_WRITE.equals(type));
90      }
91  
92      /**
93       * Checks the quality of the secret.
94       *
95       * @param secret the secret
96       * @return true if valid or false
97       */
98      public static boolean isValidSecret(final String secret) {
99          return secret.length() > 0;
100     }
101 
102     /**
103      * Encrypts secret and uses {@link MCRObjectID} as salt.
104      *
105      * @param secret the secret
106      * @param objectId the {@link MCRObjectID}
107      * @return hashed secret
108      * @throws MCRException if encryption fails
109      */
110     public static String hashSecret(final String secret, final MCRObjectID objectId) throws MCRException {
111         switch (SECRET_STORAGE_MODE) {
112         case "plain":
113             return secret;
114         case "crypt":
115             try {
116                 final MCRCipher cipher = MCRCipherManager.getCipher("accesskey");
117                 return cipher.encrypt(objectId.toString() + secret);
118             } catch (MCRCryptKeyFileNotFoundException | MCRCryptKeyNoPermissionException e) {
119                 throw new MCRException(e);
120             }
121         case "hash":
122             try {
123                 return MCRUtils.asSHA256String(HASHING_ITERATIONS, objectId.toString().getBytes(UTF_8), secret);
124             } catch (NoSuchAlgorithmException e) {
125                 throw new MCRException("Cannot hash secret.", e);
126             }
127         default:
128             throw new MCRException("Please configure a valid storage mode for secret.");
129         }
130     }
131 
132     /**
133      * Creates a {@link MCRAccessKey} for given {@link MCRObjectID}.
134      * Hashed the secret
135      *
136      * @param objectId the {@link MCRObjectID}
137      * @param accessKey access key with secret
138      * @throws MCRException key is not valid
139      */
140     public static synchronized void createAccessKey(final MCRObjectID objectId, final MCRAccessKey accessKey)
141         throws MCRException {
142         final String secret = accessKey.getSecret();
143         if (secret == null || !isValidSecret(secret)) {
144             throw new MCRAccessKeyInvalidSecretException("Incorrect secret.");
145         }
146         accessKey.setSecret(hashSecret(secret, objectId));
147         accessKey.setCreatedBy(MCRSessionMgr.getCurrentSession().getUserInformation().getUserID());
148         accessKey.setCreated(new Date());
149         accessKey.setLastModifiedBy(MCRSessionMgr.getCurrentSession().getUserInformation().getUserID());
150         accessKey.setLastModified(new Date());
151         if (accessKey.getIsActive() == null) {
152             accessKey.setIsActive(true);
153         }
154         addAccessKey(objectId, accessKey);
155     }
156 
157     /**
158      * Adds a {@link MCRAccessKey} for given {@link MCRObjectID}.
159      * Checks for a {@link MCRAccessKey} collision
160      *
161      * @param objectId the {@link MCRObjectID}
162      * @param accessKey access key with hashed secret
163      * @throws MCRException key is not valid
164      */
165     private static synchronized void addAccessKey(final MCRObjectID objectId, final MCRAccessKey accessKey)
166         throws MCRException {
167         final String secret = accessKey.getSecret();
168         if (secret == null) {
169             LOGGER.debug("Incorrect secret.");
170             throw new MCRAccessKeyInvalidSecretException("Incorrect secret.");
171         }
172         final String type = accessKey.getType();
173         if (type == null || !isValidType(type)) {
174             LOGGER.debug("Invalid permission type.");
175             throw new MCRAccessKeyInvalidTypeException("Invalid permission type.");
176         }
177         if (getAccessKeyWithSecret(objectId, secret) == null) {
178             accessKey.setId(0); //prevent collision
179             accessKey.setObjectId(objectId);
180             final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
181             em.persist(accessKey);
182             em.detach(accessKey);
183         } else {
184             LOGGER.debug("Key collision.");
185             throw new MCRAccessKeyCollisionException("Key collision.");
186         }
187     }
188 
189     /**
190      * Adds {@link MCRAccessKey} list for given {@link MCRObjectID}.
191      *
192      * @param objectId the {@link MCRObjectID}
193      * @param accessKeys the {@link MCRAccessKey} list
194      * @throws MCRAccessKeyException key is not valid
195      */
196     public static synchronized void addAccessKeys(final MCRObjectID objectId, final List<MCRAccessKey> accessKeys)
197         throws MCRAccessKeyException {
198         for (MCRAccessKey accessKey : accessKeys) { //Transaktion
199             addAccessKey(objectId, accessKey);
200         }
201     }
202 
203     /**
204      * Deletes all {@link MCRAccessKey}.
205      */
206     public static void clearAccessKeys() {
207         MCREntityManagerProvider.getCurrentEntityManager()
208             .createNamedQuery("MCRAccessKey.clear")
209             .executeUpdate();
210     }
211 
212     /**
213      * Deletes the all {@link MCRAccessKey} for given {@link MCRObjectID}.
214      *
215      * @param objectId the {@link MCRObjectID}
216      */
217     public static void clearAccessKeys(final MCRObjectID objectId) {
218         MCREntityManagerProvider.getCurrentEntityManager()
219             .createNamedQuery("MCRAccessKey.clearWithObjectId")
220             .setParameter("objectId", objectId)
221             .executeUpdate();
222     }
223 
224     /**
225      * Removes {@link MCRAccessKey} for given {@link MCRObjectID} and secret.
226      *
227      * @param objectId the {@link MCRObjectID}
228      * @param secret the secret
229      */
230     public static synchronized void removeAccessKey(final MCRObjectID objectId, final String secret)
231         throws MCRAccessKeyNotFoundException {
232         final MCRAccessKey accessKey = getAccessKeyWithSecret(objectId, secret);
233         if (accessKey == null) {
234             LOGGER.debug("Key does not exist.");
235             throw new MCRAccessKeyNotFoundException("Key does not exist.");
236         } else {
237             MCRAccessCacheHelper.clearAllPermissionCaches(objectId.toString());
238             final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
239             em.remove(em.contains(accessKey) ? accessKey : em.merge(accessKey));
240         }
241     }
242 
243     /**
244      * Updates {@link MCRAccessKey}
245      *
246      * @param objectId the {@link MCRObjectID}
247      * @param secret the access key secret 
248      * @param updatedAccessKey access key
249      * @throws MCRException if update fails
250      */
251     public static synchronized void updateAccessKey(final MCRObjectID objectId, final String secret,
252         final MCRAccessKey updatedAccessKey) throws MCRException {
253         final MCRAccessKey accessKey = getAccessKeyWithSecret(objectId, secret);
254         if (accessKey != null) {
255             final String type = updatedAccessKey.getType();
256             if (type != null && !accessKey.getType().equals(type)) {
257                 if (isValidType(type)) {
258                     MCRAccessCacheHelper.clearAllPermissionCaches(objectId.toString());
259                     accessKey.setType(type);
260                 } else {
261                     LOGGER.debug("Unkown Type.");
262                     throw new MCRAccessKeyInvalidTypeException("Unknown permission type.");
263                 }
264             }
265             final Boolean isActive = updatedAccessKey.getIsActive();
266             if (isActive != null) {
267                 MCRAccessCacheHelper.clearAllPermissionCaches(objectId.toString());
268                 accessKey.setIsActive(isActive);
269             }
270             final Date expiration = updatedAccessKey.getExpiration();
271             if (expiration != null) {
272                 MCRAccessCacheHelper.clearAllPermissionCaches(objectId.toString());
273                 accessKey.setExpiration(expiration);
274             }
275             final String comment = updatedAccessKey.getComment();
276             if (comment != null) {
277                 accessKey.setComment(comment);
278             }
279             accessKey.setLastModifiedBy(MCRSessionMgr.getCurrentSession().getUserInformation().getUserID());
280             accessKey.setLastModified(new Date());
281             final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
282             em.merge(accessKey);
283         } else {
284             LOGGER.debug("Key does not exist.");
285             throw new MCRAccessKeyNotFoundException("Key does not exist.");
286         }
287     }
288 
289     /**
290      * Return the {@link MCRAccessKey} for given {@link MCRObjectID} and secret.
291      *
292      * @param objectId the {@link MCRObjectID}
293      * @param secret the hashed secret 
294      * @return the {@link MCRAccessKey}
295      */
296     public static synchronized MCRAccessKey getAccessKeyWithSecret(final MCRObjectID objectId, final String secret) {
297         final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
298         final MCRAccessKey accessKey = em.createNamedQuery("MCRAccessKey.getWithSecret", MCRAccessKey.class)
299             .setParameter("objectId", objectId)
300             .setParameter("secret", secret)
301             .getResultList()
302             .stream()
303             .findFirst()
304             .orElse(null);
305         if (accessKey != null) {
306             em.detach(accessKey);
307         }
308         return accessKey;
309     }
310 
311     /**
312      * Return the access keys for given {@link MCRObjectID} and type.
313      *
314      * @param objectId the {@link MCRObjectID}
315      * @param type the type
316      * @return {@link MCRAccessKey} list
317      */
318     public static synchronized List<MCRAccessKey> listAccessKeysWithType(final MCRObjectID objectId,
319         final String type) {
320         final EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
321         final List<MCRAccessKey> accessKeys = em.createNamedQuery("MCRAccessKey.getWithType", MCRAccessKey.class)
322             .setParameter("objectId", objectId)
323             .setParameter("type", type)
324             .getResultList();
325         for (MCRAccessKey accessKey : accessKeys) {
326             em.detach(accessKey);
327         }
328         return accessKeys;
329     }
330 }