1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.spring.security;
20
21 import com.nimbusds.jose.JOSEException;
22 import com.nimbusds.jose.JWSAlgorithm;
23 import com.nimbusds.jose.JWSHeader;
24 import com.nimbusds.jose.jca.JCAContext;
25 import com.nimbusds.jose.util.Base64URL;
26 import com.nimbusds.jwt.JWTClaimsSet;
27 import java.time.Duration;
28 import java.time.Instant;
29 import java.time.OffsetDateTime;
30 import java.time.ZoneOffset;
31 import java.util.Set;
32 import org.apache.commons.lang3.tuple.Pair;
33 import org.apache.syncope.core.persistence.api.dao.UserDAO;
34 import org.apache.syncope.core.persistence.api.entity.user.User;
35 import org.apache.syncope.core.spring.security.jws.MSEntraAccessTokenJWSVerifier;
36 import org.springframework.transaction.annotation.Transactional;
37
38
39
40
41
42
43 public class MSEntraJWTSSOProvider implements JWTSSOProvider {
44
45 protected final UserDAO userDAO;
46
47 protected final AuthDataAccessor authDataAccessor;
48
49 protected final String tenantId;
50
51 protected final String appId;
52
53 protected final String authUsername;
54
55 protected final Duration clockSkew;
56
57 protected final MSEntraAccessTokenJWSVerifier verifier;
58
59 public MSEntraJWTSSOProvider(
60 final UserDAO userDAO,
61 final AuthDataAccessor authDataAccessor,
62 final String tenantId,
63 final String appId,
64 final String authUsername,
65 final Duration clockSkew,
66 final MSEntraAccessTokenJWSVerifier verifier) {
67
68 this.userDAO = userDAO;
69 this.authDataAccessor = authDataAccessor;
70 this.tenantId = tenantId;
71 this.appId = appId;
72 this.authUsername = authUsername;
73 this.clockSkew = clockSkew;
74 this.verifier = verifier;
75 }
76
77 @Override
78 public String getIssuer() {
79 return String.format("https://sts.windows.net/%s/", tenantId);
80 }
81
82 @Override
83 public Set<JWSAlgorithm> supportedJWSAlgorithms() {
84 return verifier.supportedJWSAlgorithms();
85 }
86
87 @Override
88 public JCAContext getJCAContext() {
89 return verifier.getJCAContext();
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 @Override
106 public boolean verify(final JWSHeader header, final byte[] signingInput, final Base64URL signature)
107 throws JOSEException {
108
109 return verifier.verify(header, signingInput, signature);
110 }
111
112 @Transactional(readOnly = true)
113 @Override
114 public Pair<User, Set<SyncopeGrantedAuthority>> resolve(final JWTClaimsSet jwtClaims) {
115 User authUser = userDAO.findByUsername(authUsername);
116 Set<SyncopeGrantedAuthority> authorities = Set.of();
117
118 Instant now = OffsetDateTime.now(ZoneOffset.UTC).toInstant();
119 Instant issued = jwtClaims.getIssueTime().toInstant();
120 Instant notBefore = jwtClaims.getNotBeforeTime().toInstant();
121 Instant expired = jwtClaims.getExpirationTime().toInstant();
122
123 if (authUser != null
124 && jwtClaims.getAudience().contains(appId)
125 && now.isAfter(issued.minus(clockSkew))
126 && now.isAfter(notBefore.minus(clockSkew))
127 && now.isBefore(expired.plus(clockSkew))) {
128
129 authorities = authDataAccessor.getAuthorities(authUser.getUsername(), null);
130 }
131
132 return Pair.of(authUser, authorities);
133 }
134 }