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  
22  package org.opencastproject.kernel.security;
23  
24  import org.opencastproject.security.api.JaxbOrganization;
25  import org.opencastproject.security.api.JaxbRole;
26  import org.opencastproject.security.api.JaxbUser;
27  import org.opencastproject.security.api.Organization;
28  import org.opencastproject.security.api.SecurityService;
29  import org.opencastproject.security.api.User;
30  import org.opencastproject.security.api.UserDirectoryService;
31  import org.opencastproject.security.util.SecurityUtil;
32  
33  import org.osgi.service.component.annotations.Component;
34  import org.osgi.service.component.annotations.Reference;
35  import org.osgi.service.component.annotations.ReferenceCardinality;
36  import org.osgi.service.component.annotations.ReferencePolicy;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import org.springframework.security.authentication.AnonymousAuthenticationToken;
40  import org.springframework.security.core.Authentication;
41  import org.springframework.security.core.GrantedAuthority;
42  import org.springframework.security.core.context.SecurityContextHolder;
43  import org.springframework.security.core.userdetails.UserDetails;
44  
45  import java.util.Collection;
46  import java.util.HashSet;
47  import java.util.Set;
48  
49  /**
50   * A Spring Security implementation of {@link SecurityService}.
51   */
52  @Component(
53      property = {
54          "service.description=Provides username and role information for the current user"
55      },
56      service = { SecurityService.class }
57  )
58  public class SecurityServiceSpringImpl implements SecurityService {
59  
60    /** The logger */
61    private static final Logger logger = LoggerFactory.getLogger(SecurityServiceSpringImpl.class);
62  
63    /** Holds delegates users for new threads that have been spawned from authenticated threads */
64    private static final ThreadLocal<User> delegatedUserHolder = new ThreadLocal<User>();
65  
66    /** Holds the IP address for the delegated user for the current thread */
67    private static final ThreadLocal<String> delegatedUserIPHolder = new ThreadLocal<String>();
68  
69    /** Holds organization responsible for the current thread */
70    private static final ThreadLocal<Organization> organization = new ThreadLocal<Organization>();
71  
72    /** The user directory */
73    private UserDirectoryService userDirectory;
74  
75    /**
76     * {@inheritDoc}
77     *
78     * @see org.opencastproject.security.api.SecurityService#getOrganization()
79     */
80    @Override
81    public Organization getOrganization() {
82      return SecurityServiceSpringImpl.organization.get();
83    }
84  
85    /**
86     * {@inheritDoc}
87     *
88     * @see org.opencastproject.security.api.SecurityService#setOrganization(Organization)
89     */
90    @Override
91    public void setOrganization(Organization organization) {
92      SecurityServiceSpringImpl.organization.set(organization);
93    }
94  
95    /**
96     * {@inheritDoc}
97     *
98     * @see org.opencastproject.security.api.SecurityService#getUser()
99     */
100   @Override
101   public User getUser() throws IllegalStateException {
102     Organization org = getOrganization();
103     if (org == null) {
104       throw new IllegalStateException("No organization is set in security context");
105     }
106 
107     User delegatedUser = delegatedUserHolder.get();
108 
109     Authentication auth = SecurityContextHolder.getContext().getAuthentication();
110     if (auth instanceof AnonymousAuthenticationToken) {
111       return SecurityUtil.createAnonymousUser(org);
112     }
113 
114     if (delegatedUser != null) {
115       return delegatedUser;
116     }
117 
118     JaxbOrganization jaxbOrganization = JaxbOrganization.fromOrganization(org);
119     if (auth != null) {
120       Object principal = auth.getPrincipal();
121       if ((principal instanceof UserDetails)) {
122         UserDetails userDetails = (UserDetails) principal;
123 
124         User user = null;
125 
126         // If user exists, fetch it from the userDirectory
127         if (userDirectory != null) {
128           user = userDirectory.loadUser(userDetails.getUsername());
129           if (user == null) {
130             logger.debug("Authenticated user '{}' could not be found in any of the current UserProviders. "
131                 + "Continuing anyway...", userDetails.getUsername());
132           }
133         } else {
134           logger.debug("No UserDirectory was found when trying to search for user '{}'", userDetails.getUsername());
135         }
136 
137         // Add the roles (authorities) in the security context
138         Set<JaxbRole> roles = new HashSet<>();
139         Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
140         if (authorities != null) {
141           for (GrantedAuthority ga : authorities) {
142             roles.add(new JaxbRole(ga.getAuthority(), jaxbOrganization));
143           }
144         }
145 
146         if (user == null) {
147           // No user was found. Create one to hold the auth information from the security context
148           user = new JaxbUser(userDetails.getUsername(), null, jaxbOrganization, roles);
149         } else {
150           // Combine the existing user with the roles in the security context
151           user = JaxbUser.fromUser(user, roles);
152         }
153 
154         // Save the user to retrieve it quicker the next time(s) this method is called (by this thread)
155         delegatedUserHolder.set(user);
156 
157         return user;
158       }
159     }
160 
161     // Return the anonymous user by default
162     return SecurityUtil.createAnonymousUser(jaxbOrganization);
163   }
164 
165   /**
166    * {@inheritDoc}
167    *
168    * @see org.opencastproject.security.api.SecurityService#setUser(User)
169    */
170   @Override
171   public void setUser(User user) {
172     delegatedUserHolder.set(user);
173   }
174 
175   @Override
176   public String getUserIP() {
177     return delegatedUserIPHolder.get();
178   }
179 
180   @Override
181   public void setUserIP(String userIP) {
182     delegatedUserIPHolder.set(userIP);
183   }
184 
185   /**
186    * OSGi callback for setting the user directory.
187    *
188    * @param userDirectory
189    *          the user directory
190    */
191   @Reference(
192       cardinality = ReferenceCardinality.OPTIONAL,
193       policy = ReferencePolicy.DYNAMIC,
194       unbind = "removeUserDirectory"
195   )
196   void setUserDirectory(UserDirectoryService userDirectory) {
197     this.userDirectory = userDirectory;
198   }
199 
200   /**
201    * OSGi callback for removing the user directory.
202    */
203   void removeUserDirectory(UserDirectoryService unused) {
204     if (this.userDirectory == unused) {
205       this.userDirectory = null;
206     }
207   }
208 
209 }