| margaretha | a452c5e | 2018-04-25 22:48:09 +0200 | [diff] [blame] | 1 | package de.ids_mannheim.korap.oauth2.dao; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 2 | |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 3 | import java.time.ZoneId; |
| 4 | import java.time.ZonedDateTime; |
| 5 | import java.util.List; |
| 6 | |
| margaretha | 6e79684 | 2023-08-17 15:10:45 +0200 | [diff] [blame] | 7 | import jakarta.persistence.EntityManager; |
| 8 | import jakarta.persistence.NoResultException; |
| 9 | import jakarta.persistence.PersistenceContext; |
| 10 | import jakarta.persistence.Query; |
| 11 | import jakarta.persistence.TypedQuery; |
| 12 | import jakarta.persistence.criteria.CriteriaBuilder; |
| 13 | import jakarta.persistence.criteria.CriteriaQuery; |
| 14 | import jakarta.persistence.criteria.Join; |
| 15 | import jakarta.persistence.criteria.Predicate; |
| 16 | import jakarta.persistence.criteria.Root; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 17 | |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 18 | import org.springframework.beans.factory.annotation.Autowired; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 19 | import org.springframework.stereotype.Repository; |
| 20 | import org.springframework.transaction.annotation.Transactional; |
| 21 | |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 22 | import com.fasterxml.jackson.databind.JsonNode; |
| 23 | |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 24 | import de.ids_mannheim.korap.config.Attributes; |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 25 | import de.ids_mannheim.korap.config.FullConfiguration; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 26 | import de.ids_mannheim.korap.exceptions.KustvaktException; |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 27 | import de.ids_mannheim.korap.exceptions.StatusCodes; |
| margaretha | a452c5e | 2018-04-25 22:48:09 +0200 | [diff] [blame] | 28 | import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType; |
| margaretha | 0afd44a | 2020-02-05 10:49:21 +0100 | [diff] [blame] | 29 | import de.ids_mannheim.korap.oauth2.entity.AccessToken; |
| 30 | import de.ids_mannheim.korap.oauth2.entity.AccessToken_; |
| margaretha | a452c5e | 2018-04-25 22:48:09 +0200 | [diff] [blame] | 31 | import de.ids_mannheim.korap.oauth2.entity.OAuth2Client; |
| 32 | import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_; |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 33 | import de.ids_mannheim.korap.oauth2.entity.RefreshToken; |
| 34 | import de.ids_mannheim.korap.oauth2.entity.RefreshToken_; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 35 | import de.ids_mannheim.korap.utils.ParameterChecker; |
| 36 | |
| margaretha | 0afd44a | 2020-02-05 10:49:21 +0100 | [diff] [blame] | 37 | /** |
| 38 | * Manages database queries and transactions regarding OAuth2 clients. |
| margaretha | 398f472 | 2019-01-09 19:07:20 +0100 | [diff] [blame] | 39 | * |
| 40 | * @author margaretha |
| 41 | * |
| 42 | */ |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 43 | @Transactional |
| 44 | @Repository |
| 45 | public class OAuth2ClientDao { |
| 46 | |
| 47 | @PersistenceContext |
| 48 | private EntityManager entityManager; |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 49 | @Autowired |
| 50 | private FullConfiguration config; |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 51 | |
| margaretha | 6d61a55 | 2018-04-10 19:26:44 +0200 | [diff] [blame] | 52 | public void registerClient (String id, String secretHashcode, String name, |
| Akron | dc6d73d | 2020-04-15 16:40:04 +0200 | [diff] [blame] | 53 | OAuth2ClientType type, String url, String redirectURI, |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 54 | String registeredBy, String description, int refreshTokenExpiry, |
| 55 | JsonNode source) throws KustvaktException { |
| margaretha | 18259ce | 2021-05-03 17:31:58 +0200 | [diff] [blame] | 56 | ParameterChecker.checkStringValue(id, "client_id"); |
| 57 | ParameterChecker.checkStringValue(name, "client_name"); |
| 58 | ParameterChecker.checkObjectValue(type, "client_type"); |
| 59 | ParameterChecker.checkStringValue(description, "client_description"); |
| margaretha | d7cab21 | 2018-07-02 19:01:43 +0200 | [diff] [blame] | 60 | // ParameterChecker.checkStringValue(url, "client url"); |
| 61 | // ParameterChecker.checkStringValue(redirectURI, "client |
| 62 | // redirect uri"); |
| margaretha | 18259ce | 2021-05-03 17:31:58 +0200 | [diff] [blame] | 63 | ParameterChecker.checkStringValue(registeredBy, "registered_by"); |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 64 | |
| 65 | OAuth2Client client = new OAuth2Client(); |
| 66 | client.setId(id); |
| 67 | client.setName(name); |
| margaretha | 6d61a55 | 2018-04-10 19:26:44 +0200 | [diff] [blame] | 68 | client.setSecret(secretHashcode); |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 69 | client.setType(type); |
| margaretha | 85273f1 | 2019-02-04 18:13:17 +0100 | [diff] [blame] | 70 | client.setUrl(url); |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 71 | client.setRedirectURI(redirectURI); |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 72 | client.setRegisteredBy(registeredBy); |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 73 | client.setRegistrationDate(ZonedDateTime.now()); |
| margaretha | fb027f9 | 2018-04-23 20:00:13 +0200 | [diff] [blame] | 74 | client.setDescription(description); |
| margaretha | 4ff862a | 2022-06-03 12:40:21 +0200 | [diff] [blame] | 75 | if (source != null && !source.isNull()) { |
| 76 | if (type.equals(OAuth2ClientType.CONFIDENTIAL)) { |
| 77 | client.setSource(source.toString()); |
| 78 | } |
| 79 | else { |
| 80 | throw new KustvaktException(StatusCodes.NOT_SUPPORTED, |
| 81 | "Only confidential plugins are supported."); |
| 82 | } |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 83 | } |
| 84 | else { |
| 85 | client.setPermitted(true); |
| 86 | } |
| margaretha | 7d7e25e | 2022-05-27 08:41:25 +0200 | [diff] [blame] | 87 | if (refreshTokenExpiry <= 0) { |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 88 | if (type.equals(OAuth2ClientType.CONFIDENTIAL)) { |
| margaretha | 7d7e25e | 2022-05-27 08:41:25 +0200 | [diff] [blame] | 89 | refreshTokenExpiry = config.getRefreshTokenLongExpiry(); |
| 90 | } |
| margaretha | d716312 | 2022-04-11 09:42:41 +0200 | [diff] [blame] | 91 | } |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 92 | else if (type.equals(OAuth2ClientType.PUBLIC)) { |
| 93 | throw new KustvaktException( |
| 94 | StatusCodes.INVALID_REFRESH_TOKEN_EXPIRY, |
| margaretha | 7d7e25e | 2022-05-27 08:41:25 +0200 | [diff] [blame] | 95 | "Custom refresh token expiry is only applicable for confidential clients"); |
| 96 | } |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 97 | else if (refreshTokenExpiry > 31536000) { |
| 98 | throw new KustvaktException( |
| 99 | StatusCodes.INVALID_REFRESH_TOKEN_EXPIRY, |
| margaretha | 7d7e25e | 2022-05-27 08:41:25 +0200 | [diff] [blame] | 100 | "Maximum refresh token expiry is 31536000 seconds (1 year)"); |
| 101 | } |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 102 | |
| margaretha | 977fabe | 2022-04-28 09:23:47 +0200 | [diff] [blame] | 103 | client.setRefreshTokenExpiry(refreshTokenExpiry); |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 104 | entityManager.persist(client); |
| 105 | } |
| 106 | |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 107 | public OAuth2Client retrieveClientById (String clientId) |
| margaretha | 0512231 | 2018-04-16 15:01:34 +0200 | [diff] [blame] | 108 | throws KustvaktException { |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 109 | |
| margaretha | a048627 | 2018-04-12 19:59:31 +0200 | [diff] [blame] | 110 | ParameterChecker.checkStringValue(clientId, "client_id"); |
| margaretha | 0512231 | 2018-04-16 15:01:34 +0200 | [diff] [blame] | 111 | |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 112 | CriteriaBuilder builder = entityManager.getCriteriaBuilder(); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 113 | CriteriaQuery<OAuth2Client> query = builder |
| 114 | .createQuery(OAuth2Client.class); |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 115 | |
| 116 | Root<OAuth2Client> root = query.from(OAuth2Client.class); |
| 117 | query.select(root); |
| 118 | query.where(builder.equal(root.get(OAuth2Client_.id), clientId)); |
| 119 | |
| 120 | Query q = entityManager.createQuery(query); |
| margaretha | 0512231 | 2018-04-16 15:01:34 +0200 | [diff] [blame] | 121 | try { |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 122 | return (OAuth2Client) q.getSingleResult(); |
| margaretha | 0512231 | 2018-04-16 15:01:34 +0200 | [diff] [blame] | 123 | } |
| margaretha | f839dde | 2018-04-16 17:52:57 +0200 | [diff] [blame] | 124 | catch (NoResultException e) { |
| 125 | throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND, |
| margaretha | 7da2390 | 2022-05-02 08:38:45 +0200 | [diff] [blame] | 126 | "Unknown client: " + clientId, "invalid_client"); |
| margaretha | f839dde | 2018-04-16 17:52:57 +0200 | [diff] [blame] | 127 | } |
| margaretha | 0512231 | 2018-04-16 15:01:34 +0200 | [diff] [blame] | 128 | catch (Exception e) { |
| 129 | throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND, |
| 130 | e.getMessage(), "invalid_client"); |
| 131 | } |
| margaretha | 0e8f4e7 | 2018-04-05 14:11:52 +0200 | [diff] [blame] | 132 | } |
| 133 | |
| margaretha | 835178d | 2018-08-15 19:04:03 +0200 | [diff] [blame] | 134 | public void deregisterClient (OAuth2Client client) |
| 135 | throws KustvaktException { |
| margaretha | 7f5071f | 2018-08-14 15:58:51 +0200 | [diff] [blame] | 136 | ParameterChecker.checkObjectValue(client, "client"); |
| margaretha | 8d804f6 | 2018-04-10 12:39:56 +0200 | [diff] [blame] | 137 | if (!entityManager.contains(client)) { |
| 138 | client = entityManager.merge(client); |
| 139 | } |
| 140 | entityManager.remove(client); |
| 141 | } |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 142 | |
| margaretha | 7f5071f | 2018-08-14 15:58:51 +0200 | [diff] [blame] | 143 | public void updateClient (OAuth2Client client) throws KustvaktException { |
| 144 | ParameterChecker.checkObjectValue(client, "client"); |
| 145 | client = entityManager.merge(client); |
| 146 | } |
| 147 | |
| margaretha | 7a09e48 | 2019-11-14 14:34:07 +0100 | [diff] [blame] | 148 | public List<OAuth2Client> retrieveUserAuthorizedClients (String username) |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 149 | throws KustvaktException { |
| 150 | ParameterChecker.checkStringValue(username, "username"); |
| 151 | |
| 152 | CriteriaBuilder builder = entityManager.getCriteriaBuilder(); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 153 | CriteriaQuery<OAuth2Client> query = builder |
| 154 | .createQuery(OAuth2Client.class); |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 155 | |
| 156 | Root<OAuth2Client> client = query.from(OAuth2Client.class); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 157 | Join<OAuth2Client, RefreshToken> refreshToken = client |
| 158 | .join(OAuth2Client_.refreshTokens); |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 159 | Predicate condition = builder.and( |
| 160 | builder.equal(refreshToken.get(RefreshToken_.userId), username), |
| 161 | builder.equal(refreshToken.get(RefreshToken_.isRevoked), false), |
| 162 | builder.greaterThan( |
| 163 | refreshToken |
| 164 | .<ZonedDateTime> get(RefreshToken_.expiryDate), |
| 165 | ZonedDateTime |
| 166 | .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE)))); |
| 167 | query.select(client); |
| 168 | query.where(condition); |
| margaretha | 5a2c34e | 2018-11-29 19:35:13 +0100 | [diff] [blame] | 169 | query.distinct(true); |
| margaretha | 230effb | 2018-11-29 17:28:18 +0100 | [diff] [blame] | 170 | TypedQuery<OAuth2Client> q = entityManager.createQuery(query); |
| 171 | return q.getResultList(); |
| 172 | } |
| 173 | |
| margaretha | 0afd44a | 2020-02-05 10:49:21 +0100 | [diff] [blame] | 174 | public List<OAuth2Client> retrieveClientsByAccessTokens (String username) |
| 175 | throws KustvaktException { |
| 176 | ParameterChecker.checkStringValue(username, "username"); |
| 177 | |
| 178 | CriteriaBuilder builder = entityManager.getCriteriaBuilder(); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 179 | CriteriaQuery<OAuth2Client> query = builder |
| 180 | .createQuery(OAuth2Client.class); |
| margaretha | 0afd44a | 2020-02-05 10:49:21 +0100 | [diff] [blame] | 181 | |
| 182 | Root<OAuth2Client> client = query.from(OAuth2Client.class); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 183 | Join<OAuth2Client, AccessToken> accessToken = client |
| 184 | .join(OAuth2Client_.accessTokens); |
| margaretha | 0afd44a | 2020-02-05 10:49:21 +0100 | [diff] [blame] | 185 | Predicate condition = builder.and( |
| 186 | builder.equal(accessToken.get(AccessToken_.userId), username), |
| 187 | builder.equal(accessToken.get(AccessToken_.isRevoked), false), |
| 188 | builder.greaterThan( |
| 189 | accessToken |
| 190 | .<ZonedDateTime> get(AccessToken_.expiryDate), |
| 191 | ZonedDateTime |
| 192 | .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE)))); |
| 193 | query.select(client); |
| 194 | query.where(condition); |
| 195 | query.distinct(true); |
| 196 | TypedQuery<OAuth2Client> q = entityManager.createQuery(query); |
| 197 | return q.getResultList(); |
| 198 | } |
| 199 | |
| margaretha | 7a09e48 | 2019-11-14 14:34:07 +0100 | [diff] [blame] | 200 | public List<OAuth2Client> retrieveUserRegisteredClients (String username) |
| 201 | throws KustvaktException { |
| 202 | ParameterChecker.checkStringValue(username, "username"); |
| 203 | |
| 204 | CriteriaBuilder builder = entityManager.getCriteriaBuilder(); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 205 | CriteriaQuery<OAuth2Client> query = builder |
| 206 | .createQuery(OAuth2Client.class); |
| margaretha | 7a09e48 | 2019-11-14 14:34:07 +0100 | [diff] [blame] | 207 | |
| 208 | Root<OAuth2Client> client = query.from(OAuth2Client.class); |
| 209 | query.select(client); |
| 210 | query.where(builder.equal(client.get(OAuth2Client_.registeredBy), |
| 211 | username)); |
| 212 | query.distinct(true); |
| 213 | TypedQuery<OAuth2Client> q = entityManager.createQuery(query); |
| 214 | return q.getResultList(); |
| 215 | } |
| 216 | |
| margaretha | e20a280 | 2022-04-21 12:37:38 +0200 | [diff] [blame] | 217 | public List<OAuth2Client> retrievePlugins (boolean isPermittedOnly) |
| 218 | throws KustvaktException { |
| 219 | CriteriaBuilder builder = entityManager.getCriteriaBuilder(); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 220 | CriteriaQuery<OAuth2Client> query = builder |
| 221 | .createQuery(OAuth2Client.class); |
| margaretha | e20a280 | 2022-04-21 12:37:38 +0200 | [diff] [blame] | 222 | |
| 223 | Root<OAuth2Client> client = query.from(OAuth2Client.class); |
| margaretha | 35e1ca2 | 2023-11-16 22:00:01 +0100 | [diff] [blame] | 224 | Predicate restrictions = builder |
| 225 | .isNotNull(client.get(OAuth2Client_.SOURCE)); |
| margaretha | e20a280 | 2022-04-21 12:37:38 +0200 | [diff] [blame] | 226 | if (isPermittedOnly) { |
| 227 | restrictions = builder.and(restrictions, |
| 228 | builder.isTrue(client.get(OAuth2Client_.IS_PERMITTED))); |
| 229 | } |
| 230 | |
| 231 | query.select(client); |
| 232 | query.where(restrictions); |
| 233 | query.distinct(true); |
| 234 | TypedQuery<OAuth2Client> q = entityManager.createQuery(query); |
| 235 | return q.getResultList(); |
| 236 | } |
| 237 | |
| margaretha | 31a9f52 | 2018-04-03 20:40:45 +0200 | [diff] [blame] | 238 | } |