View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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             // no AccessToken found: create new
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             // AccessToken found, but either replace was requested or it is expired: update existing
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 }