1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java.data;
20
21 import com.nimbusds.jose.JOSEException;
22 import com.nimbusds.jose.JWSHeader;
23 import com.nimbusds.jwt.JWTClaimsSet;
24 import com.nimbusds.jwt.SignedJWT;
25 import java.text.ParseException;
26 import java.time.OffsetDateTime;
27 import java.util.Date;
28 import java.util.Map;
29 import org.apache.commons.lang3.tuple.Pair;
30 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
31 import org.apache.syncope.common.lib.SyncopeClientException;
32 import org.apache.syncope.common.lib.to.AccessTokenTO;
33 import org.apache.syncope.common.lib.types.ClientExceptionType;
34 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
35 import org.apache.syncope.core.persistence.api.entity.AccessToken;
36 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
37 import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
38 import org.apache.syncope.core.spring.security.AuthContextUtils;
39 import org.apache.syncope.core.spring.security.DefaultCredentialChecker;
40 import org.apache.syncope.core.spring.security.SecureRandomUtils;
41 import org.apache.syncope.core.spring.security.SecurityProperties;
42 import org.apache.syncope.core.spring.security.jws.AccessTokenJWSSigner;
43
44 public class AccessTokenDataBinderImpl implements AccessTokenDataBinder {
45
46 protected final SecurityProperties securityProperties;
47
48 protected final AccessTokenJWSSigner jwsSigner;
49
50 protected final AccessTokenDAO accessTokenDAO;
51
52 protected final ConfParamOps confParamOps;
53
54 protected final EntityFactory entityFactory;
55
56 protected final DefaultCredentialChecker credentialChecker;
57
58 public AccessTokenDataBinderImpl(
59 final SecurityProperties securityProperties,
60 final AccessTokenJWSSigner jwsSigner,
61 final AccessTokenDAO accessTokenDAO,
62 final ConfParamOps confParamOps,
63 final EntityFactory entityFactory,
64 final DefaultCredentialChecker credentialChecker) {
65
66 this.securityProperties = securityProperties;
67 this.jwsSigner = jwsSigner;
68 this.accessTokenDAO = accessTokenDAO;
69 this.confParamOps = confParamOps;
70 this.entityFactory = entityFactory;
71 this.credentialChecker = credentialChecker;
72 }
73
74 @Override
75 public Pair<String, OffsetDateTime> generateJWT(
76 final String tokenId,
77 final String subject,
78 final long duration,
79 final Map<String, Object> claims) {
80
81 credentialChecker.checkIsDefaultJWSKeyInUse();
82
83 OffsetDateTime currentTime = OffsetDateTime.now();
84 Date issueTime = new Date(currentTime.toInstant().toEpochMilli());
85
86 OffsetDateTime expiration = currentTime.plusMinutes(duration);
87
88 JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder().
89 jwtID(tokenId).
90 subject(subject).
91 issuer(securityProperties.getJwtIssuer()).
92 issueTime(issueTime).
93 expirationTime(new Date(expiration.toInstant().toEpochMilli())).
94 notBeforeTime(issueTime);
95 claims.forEach(claimsSet::claim);
96
97 SignedJWT jwt = new SignedJWT(new JWSHeader(jwsSigner.getJwsAlgorithm()), claimsSet.build());
98 try {
99 jwt.sign(jwsSigner);
100 } catch (JOSEException e) {
101 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidAccessToken);
102 sce.getElements().add(e.getMessage());
103 throw sce;
104 }
105 return Pair.of(jwt.serialize(), expiration);
106 }
107
108 private AccessToken replace(
109 final String subject,
110 final Map<String, Object> claims,
111 final byte[] authorities,
112 final AccessToken accessToken) {
113
114 Pair<String, OffsetDateTime> generated = generateJWT(
115 accessToken.getKey(),
116 subject,
117 confParamOps.get(AuthContextUtils.getDomain(), "jwt.lifetime.minutes", 120L, Long.class),
118 claims);
119
120 accessToken.setBody(generated.getLeft());
121 accessToken.setExpirationTime(generated.getRight());
122 accessToken.setOwner(subject);
123
124 if (!securityProperties.getAdminUser().equals(accessToken.getOwner())) {
125 accessToken.setAuthorities(authorities);
126 }
127
128 return accessTokenDAO.save(accessToken);
129 }
130
131 @Override
132 public Pair<String, OffsetDateTime> create(
133 final String subject,
134 final Map<String, Object> claims,
135 final byte[] authorities,
136 final boolean replace) {
137
138 AccessToken accessToken = accessTokenDAO.findByOwner(subject);
139 if (accessToken == null) {
140
141 accessToken = entityFactory.newEntity(AccessToken.class);
142 accessToken.setKey(SecureRandomUtils.generateRandomUUID().toString());
143
144 accessToken = replace(subject, claims, authorities, accessToken);
145 } else if (replace
146 || accessToken.getExpirationTime() == null
147 || accessToken.getExpirationTime().isBefore(OffsetDateTime.now())) {
148
149
150 accessToken = replace(subject, claims, authorities, accessToken);
151 }
152
153 return Pair.of(accessToken.getBody(), accessToken.getExpirationTime());
154 }
155
156 @Override
157 public Pair<String, OffsetDateTime> update(final AccessToken accessToken, final byte[] authorities) {
158 credentialChecker.checkIsDefaultJWSKeyInUse();
159
160 long duration = confParamOps.get(AuthContextUtils.getDomain(), "jwt.lifetime.minutes", 120L, Long.class);
161
162 OffsetDateTime currentTime = OffsetDateTime.now();
163
164 OffsetDateTime expiration = currentTime.plusMinutes(duration);
165
166 SignedJWT jwt;
167 try {
168 JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder(
169 SignedJWT.parse(accessToken.getBody()).getJWTClaimsSet()).
170 expirationTime(new Date(expiration.toInstant().toEpochMilli()));
171
172 jwt = new SignedJWT(new JWSHeader(jwsSigner.getJwsAlgorithm()), claimsSet.build());
173 jwt.sign(jwsSigner);
174 } catch (ParseException | JOSEException e) {
175 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidAccessToken);
176 sce.getElements().add(e.getMessage());
177 throw sce;
178 }
179 String body = jwt.serialize();
180
181 accessToken.setBody(body);
182 accessToken.setExpirationTime(expiration);
183
184 if (!securityProperties.getAdminUser().equals(accessToken.getOwner())) {
185 accessToken.setAuthorities(authorities);
186 }
187
188 accessTokenDAO.save(accessToken);
189
190 return Pair.of(body, expiration);
191 }
192
193 @Override
194 public AccessTokenTO getAccessTokenTO(final AccessToken accessToken) {
195 AccessTokenTO accessTokenTO = new AccessTokenTO();
196 accessTokenTO.setKey(accessToken.getKey());
197 accessTokenTO.setBody(accessToken.getBody());
198 accessTokenTO.setExpirationTime(accessToken.getExpirationTime());
199 accessTokenTO.setOwner(accessToken.getOwner());
200
201 return accessTokenTO;
202 }
203 }