View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.security.aai;
22  
23  import org.opencastproject.security.aai.api.AttributeMapper;
24  import org.opencastproject.security.api.GroupProvider;
25  import org.opencastproject.security.api.JaxbOrganization;
26  import org.opencastproject.security.api.JaxbRole;
27  import org.opencastproject.security.api.Organization;
28  import org.opencastproject.security.api.Role;
29  import org.opencastproject.security.api.SecurityService;
30  import org.opencastproject.security.api.User;
31  import org.opencastproject.security.api.UserProvider;
32  import org.opencastproject.security.impl.jpa.JpaOrganization;
33  import org.opencastproject.security.impl.jpa.JpaRole;
34  import org.opencastproject.security.impl.jpa.JpaUserReference;
35  import org.opencastproject.security.shibboleth.ShibbolethLoginHandler;
36  import org.opencastproject.userdirectory.api.AAIRoleProvider;
37  import org.opencastproject.userdirectory.api.UserReferenceProvider;
38  
39  import org.apache.commons.lang3.StringUtils;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  import org.springframework.beans.factory.InitializingBean;
43  import org.springframework.security.core.userdetails.UsernameNotFoundException;
44  
45  import java.nio.charset.StandardCharsets;
46  import java.util.ArrayList;
47  import java.util.Date;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Set;
52  import java.util.regex.Pattern;
53  
54  import javax.servlet.http.HttpServletRequest;
55  
56  /**
57   * Dynamic login with Shibboleth data through SpEL mappings
58   */
59  public class DynamicLoginHandler implements ShibbolethLoginHandler, AAIRoleProvider, GroupProvider, InitializingBean {
60  
61    /** The logging facility */
62    private static final Logger logger = LoggerFactory.getLogger(DynamicLoginHandler.class);
63  
64    /** The user reference provider */
65    private UserReferenceProvider userReferenceProvider = null;
66  
67    /** The security service */
68    private SecurityService securityService = null;
69  
70    /** The security service */
71    private AttributeMapper attributeMapper = null;
72  
73    public DynamicLoginHandler() {
74    }
75  
76    /**
77     * Handle a new user login.
78     *
79     * @param id
80     *          The identity of the user, ideally the Shibboleth persistent unique identifier
81     * @param request
82     *          The request, for accessing any other Shibboleth variables
83     */
84    @Override
85    public void newUserLogin(String id, HttpServletRequest request) {
86      String name = extractName(request);
87      String email = extractEmail(request);
88      Date loginDate = new Date();
89      JpaOrganization organization = fromOrganization(securityService.getOrganization());
90  
91      // Compile the list of roles
92      Set<JpaRole> roles = extractRoles(id, request);
93  
94      // Create the user reference
95      JpaUserReference userReference = new JpaUserReference(id, name, email, MECH_SHIBBOLETH, loginDate, organization,
96              roles);
97  
98      logger.debug("Shibboleth user '{}' logged in for the first time", id);
99      userReferenceProvider.addUserReference(userReference, MECH_SHIBBOLETH);
100   }
101 
102   /**
103    * Handle an existing user login.
104    *
105    * @param id
106    *          The identity of the user, ideally the Shibboleth persistent unique identifier
107    * @param request
108    *          The request, for accessing any other Shibboleth variables
109    */
110   @Override
111   public void existingUserLogin(String id, HttpServletRequest request) {
112     Organization organization = securityService.getOrganization();
113 
114     // Load the user reference
115     JpaUserReference userReference = userReferenceProvider.findUserReference(id, organization.getId());
116     if (userReference == null) {
117       //Triggers creation of user reference
118       //Possible problem: if there is user (not reference) with that id, we will get conflicts
119       throw new UsernameNotFoundException("User reference '" + id + "' was not found");
120     }
121 
122     // Update the reference
123     userReference.setName(extractName(request));
124     userReference.setEmail(extractEmail(request));
125     userReference.setLastLogin(new Date());
126     Set<JpaRole> roles = extractRoles(id, request);
127     userReference.setRoles(roles);
128 
129     logger.debug("Shibboleth user '{}' logged in", id);
130     userReferenceProvider.updateUserReference(userReference);
131   }
132 
133   /**
134    * Sets the security service.
135    *
136    * @param securityService
137    *          the security service
138    */
139   public void setSecurityService(SecurityService securityService) {
140     this.securityService = securityService;
141   }
142 
143   /**
144    * Sets the user reference provider.
145    *
146    * @param userReferenceProvider
147    *          the user reference provider
148    */
149   public void setUserReferenceProvider(UserReferenceProvider userReferenceProvider) {
150     this.userReferenceProvider = userReferenceProvider;
151   }
152 
153   /**
154    * Extracts the name from the request.
155    *
156    * @param request
157    *          the request
158    * @return the name
159    */
160   private String extractName(HttpServletRequest request) {
161     String displayName = extractDisplayName(request);
162     if (StringUtils.isNotBlank(displayName)) {
163       return displayName;
164     }
165     return null;
166   }
167 
168   /**
169    * Extracts the e-mail from the request.
170    *
171    * @param request
172    *          the request
173    * @return the e-mail address
174    */
175   private String extractEmail(HttpServletRequest request) {
176     List<String> mailAdresses = attributeMapper.getMappedAttributes(request, "mail");
177 
178     if (mailAdresses.size() == 0) {
179       return null;
180     }
181 
182     String mailValue = mailAdresses.get(0);
183     String mail = StringUtils.isBlank(mailValue) ? ""
184             : new String(mailValue.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
185     return mail;
186   }
187 
188   /**
189    * Extracts the e-mail from the request.
190    *
191    * @param request
192    *          the request
193    * @return the e-mail address
194    */
195   private String extractDisplayName(HttpServletRequest request) {
196     List<String> displayNames = attributeMapper.getMappedAttributes(request, "displayName");
197 
198     if (displayNames.size() == 0) {
199       return null;
200     }
201 
202     String displayNameValue = displayNames.get(0);
203     String displayName = StringUtils.isBlank(displayNameValue) ? ""
204               : new String(displayNameValue.getBytes(StandardCharsets.ISO_8859_1),
205                     StandardCharsets.UTF_8);
206     return displayName;
207   }
208 
209   /**
210    * Extracts the roles from the request.
211    *
212    * @param request
213    *          the request
214    * @return the roles
215    */
216   private Set<JpaRole> extractRoles(String id, HttpServletRequest request) {
217     List<String> aaiRoles = attributeMapper.getMappedAttributes(request, "roles");
218     JpaOrganization organization = fromOrganization(securityService.getOrganization());
219     Set<JpaRole> roles = new HashSet<JpaRole>();
220     if (aaiRoles != null) {
221       for (String aaiRole : aaiRoles) {
222         roles.add(new JpaRole(aaiRole, organization));
223       }
224     }
225 
226     return roles;
227   }
228 
229   /**
230    * Creates a JpaOrganization from an organization
231    *
232    * @param org
233    *          the organization
234    */
235   private JpaOrganization fromOrganization(Organization org) {
236     if (org instanceof JpaOrganization) {
237       return (JpaOrganization) org;
238     }
239 
240     return new JpaOrganization(org.getId(), org.getName(), org.getServers(), org.getAdminRole(),
241         org.getAnonymousRole(), org.getProperties());
242   }
243 
244   /**
245    * {@inheritDoc}
246    *
247    * @see org.opencastproject.userdirectory.api.AAIRoleProvider#getRoles()
248    */
249   @Override
250   public Iterator<Role> getRoles() {
251     JaxbOrganization organization = JaxbOrganization.fromOrganization(securityService.getOrganization());
252     HashSet<Role> roles = new HashSet<Role>();
253     roles.add(new JaxbRole(organization.getAnonymousRole(), organization));
254     roles.addAll(securityService.getUser().getRoles());
255     return roles.iterator();
256   }
257 
258   /**
259    * @see org.opencastproject.security.api.RoleProvider#getRolesForUser(String)
260    */
261   @Override
262   public List<Role> getRolesForUser(String userName) {
263     ArrayList<Role> roles = new ArrayList<Role>();
264     User user = userReferenceProvider.loadUser(userName);
265     if (user != null) {
266       roles.addAll(user.getRoles());
267     }
268     return roles;
269   }
270 
271   /**
272    * @see org.opencastproject.security.api.RoleProvider#getOrganization()
273    */
274   @Override
275   public String getOrganization() {
276     return UserProvider.ALL_ORGANIZATIONS;
277   }
278 
279   /**
280    * @see org.opencastproject.security.api.RoleProvider#findRoles(String, Role.Target, int, int)
281    */
282   @Override
283   public Iterator<Role> findRoles(String query, Role.Target target, int offset, int limit) {
284     if (query == null) {
285       throw new IllegalArgumentException("Query must be set");
286     }
287     HashSet<Role> foundRoles = new HashSet<Role>();
288     for (Iterator<Role> it = getRoles(); it.hasNext();) {
289       Role role = it.next();
290       if (like(role.getName(), query) || like(role.getDescription(), query)) {
291         foundRoles.add(role);
292       }
293     }
294     return offsetLimitCollection(offset, limit, foundRoles).iterator();
295 
296   }
297 
298   private <T> HashSet<T> offsetLimitCollection(int offset, int limit, HashSet<T> entries) {
299     HashSet<T> result = new HashSet<T>();
300     int i = 0;
301     for (T entry : entries) {
302       if (limit != 0 && result.size() >= limit) {
303         break;
304       }
305       if (i >= offset) {
306         result.add(entry);
307       }
308       i++;
309     }
310     return result;
311   }
312 
313   private boolean like(String string, final String query) {
314     if (string == null) {
315       return false;
316     }
317     String regex = query.replace("_", ".").replace("%", ".*?");
318     Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
319     return p.matcher(string).matches();
320   }
321 
322   @Override
323   public void afterPropertiesSet() throws Exception {
324     this.userReferenceProvider.setRoleProvider(this);
325   }
326 
327   public AttributeMapper getAttributeMapper() {
328     return attributeMapper;
329   }
330 
331   public void setAttributeMapper(AttributeMapper attributeMapper) {
332     this.attributeMapper = attributeMapper;
333   }
334 
335   @Override
336   public List<Role> getRolesForGroup(String groupName) {
337     return null;
338   }
339 
340 }