]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
0a2aa3564ab77c9cc6e3bdcb06e654e4d0bfdff9
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.common.security;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Set;
6 import org.apache.commons.lang3.StringUtils;
7 import org.collectionspace.authentication.CSpaceUser;
8 import org.collectionspace.authentication.spring.CSpaceSaml2Authentication;
9 import org.collectionspace.authentication.spring.CSpaceUserDetailsService;
10 import org.collectionspace.services.common.config.ConfigUtils;
11 import org.collectionspace.services.config.AssertionProbesType;
12 import org.collectionspace.services.config.SAMLRelyingPartyType;
13 import org.collectionspace.services.config.ServiceConfig;
14 import org.collectionspace.services.common.ServiceMain;
15 import org.opensaml.saml.saml2.core.Assertion;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.springframework.core.convert.converter.Converter;
19 import org.springframework.security.core.userdetails.UsernameNotFoundException;
20 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
21 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider.ResponseToken;
22 import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
23
24 public class CSpaceSaml2ResponseAuthenticationConverter implements Converter<ResponseToken, CSpaceSaml2Authentication> {
25   private final Logger logger = LoggerFactory.getLogger(CSpaceSaml2ResponseAuthenticationConverter.class);
26
27   private CSpaceUserDetailsService userDetailsService;
28
29   public CSpaceSaml2ResponseAuthenticationConverter(CSpaceUserDetailsService userDetailsService) {
30     this.userDetailsService = userDetailsService;
31   }
32
33   @Override
34   public CSpaceSaml2Authentication convert(ResponseToken responseToken) {
35     Saml2Authentication authentication = OpenSamlAuthenticationProvider
36       .createDefaultResponseAuthenticationConverter()
37       .convert(responseToken);
38
39     String registrationId = responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
40     ServiceConfig serviceConfig = ServiceMain.getInstance().getServiceConfig();
41     SAMLRelyingPartyType relyingPartyRegistration = ConfigUtils.getSAMLRelyingPartyRegistration(serviceConfig, registrationId);
42     CSpaceUser user = findUser(relyingPartyRegistration, responseToken);
43
44     if (user != null) {
45       return new CSpaceSaml2Authentication(user, authentication);
46     }
47
48     return null;
49   }
50
51   /**
52    * Attempt to find a CSpace user for a SAML response.
53    *
54    * @param relyingPartyRegistration
55    * @param responseToken
56    * @return
57    */
58   private CSpaceUser findUser(SAMLRelyingPartyType relyingPartyRegistration, ResponseToken responseToken) {
59     AssertionProbesType assertionSsoIdProbes = (
60       relyingPartyRegistration != null
61         ? relyingPartyRegistration.getAssertionSsoIdProbes()
62         : null
63     );
64
65     AssertionProbesType assertionUsernameProbes = (
66       relyingPartyRegistration != null
67         ? relyingPartyRegistration.getAssertionUsernameProbes()
68         : null
69     );
70
71     List<String> attemptedUsernames = new ArrayList<>();
72     List<Assertion> assertions = responseToken.getResponse().getAssertions();
73
74     SecurityUtils.logSamlAssertions(assertions);
75
76     for (Assertion assertion : assertions) {
77       CSpaceUser user = null;
78       String ssoId = SecurityUtils.getSamlAssertionSsoId(assertion, assertionSsoIdProbes);
79
80       // First, look for a CSpace user whose SSO ID is the ID in the assertion.
81
82       if (ssoId != null) {
83         try {
84           user = (CSpaceUser) userDetailsService.loadUserBySsoId(ssoId);
85         }
86         catch (UsernameNotFoundException e) {
87         }
88       }
89
90       if (user != null) {
91         return user;
92       }
93
94       // Next, look for a CSpace user whose username is the email address in the assertion.
95
96       Set<String> candidateUsernames = SecurityUtils.findSamlAssertionCandidateUsernames(assertion, assertionUsernameProbes);
97
98       for (String candidateUsername : candidateUsernames) {
99         try {
100           user = (CSpaceUser) userDetailsService.loadUserByUsername(candidateUsername);
101
102           if (user != null) {
103             String expectedSsoId = user.getSsoId();
104
105             if (expectedSsoId == null) {
106               // Store the ID from the IdP to use in future log ins. Note that this does not save
107               // the SSO ID to the database. That happens in CSpaceAuthenticationSuccessEvent.
108
109               user.setSsoId(ssoId);
110
111               // TODO: If the email address in the assertion differs from the CSpace user's email,
112               // update the CSpace user.
113             } else if (!StringUtils.equals(expectedSsoId, ssoId)) {
114               // If the user previously logged in via SSO, but they had a different ID from the
115               // IdP, something's wrong. (Did an account on the IdP get assigned an email that
116               // previously belonged to a different account on the IdP?)
117
118               logger.warn("User with username {} has expected SSO ID {}, but received {} in SAML assertion",
119                 candidateUsername, expectedSsoId, ssoId);
120
121               user = null;
122             }
123
124             if (user != null) {
125               return user;
126             }
127           }
128         }
129         catch(UsernameNotFoundException e) {
130         }
131       }
132
133       attemptedUsernames.addAll(candidateUsernames);
134     }
135
136     // No CSpace user was found for this SAML response.
137     // TODO: Auto-create a CSpace user, using the display name, email address, and ID in the response.
138
139     String errorMessage = attemptedUsernames.size() == 0
140       ? "The SAML response did not contain a CollectionSpace username."
141       : "No CollectionSpace account found for " + StringUtils.join(attemptedUsernames, " / ") + ".";
142
143     throw(new UsernameNotFoundException(errorMessage));
144   }
145 }