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.userdirectory;
23  
24  import org.opencastproject.security.api.JaxbOrganization;
25  import org.opencastproject.security.api.JaxbRole;
26  import org.opencastproject.security.api.Organization;
27  import org.opencastproject.security.api.Role;
28  import org.opencastproject.security.api.RoleProvider;
29  import org.opencastproject.security.api.SecurityService;
30  import org.opencastproject.security.api.User;
31  import org.opencastproject.security.api.UserDirectoryService;
32  import org.opencastproject.security.api.UserProvider;
33  import org.opencastproject.util.OsgiUtil;
34  
35  import com.google.common.base.CharMatcher;
36  
37  import org.apache.commons.lang3.BooleanUtils;
38  import org.osgi.service.cm.ConfigurationException;
39  import org.osgi.service.cm.ManagedService;
40  import org.osgi.service.component.annotations.Component;
41  import org.osgi.service.component.annotations.Reference;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.Dictionary;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Optional;
52  import java.util.regex.Pattern;
53  
54  /**
55   * The user id role provider assigns the user id role.
56   */
57  @Component(
58      property = {
59          "service.description=Provides the user id role"
60      },
61      immediate = true,
62      service = { RoleProvider.class, UserIdRoleProvider.class, ManagedService.class }
63  )
64  public class UserIdRoleProvider implements RoleProvider, ManagedService {
65  
66  
67    private static final String ROLE_USER = "ROLE_USER";
68  
69    private static final String ROLE_USER_PREFIX_KEY = "role.user.prefix";
70    private static final String DEFAULT_ROLE_USER_PREFIX = "ROLE_USER_";
71  
72    private static final String SANITIZE_KEY = "sanitize";
73    private static final boolean DEFAULT_SANITIZE = true;
74  
75    private static final CharMatcher SAFE_USERNAME = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z'))
76            .or(CharMatcher.inRange('0', '9')).negate().precomputed();
77  
78    /** The logger */
79    private static final Logger logger = LoggerFactory.getLogger(UserIdRoleProvider.class);
80  
81    /** The security service */
82    protected SecurityService securityService = null;
83  
84    private static String userRolePrefix = DEFAULT_ROLE_USER_PREFIX;
85    private static boolean sanitize = DEFAULT_SANITIZE;
86  
87    /** The user directory service */
88    protected UserDirectoryService userDirectoryService = null;
89  
90    /**
91     * @param securityService
92     *          the securityService to set
93     */
94    @Reference
95    public void setSecurityService(SecurityService securityService) {
96      this.securityService = securityService;
97    }
98  
99    /**
100    * Sets the user directory service
101    *
102    * @param userDirectoryService
103    *          the userDirectoryService to set
104    */
105   @Reference
106   public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
107     this.userDirectoryService = userDirectoryService;
108   }
109 
110   public static boolean isSanitize() {
111     return sanitize;
112   };
113 
114   public static String getUserRolePrefix() {
115     return userRolePrefix;
116   };
117 
118   public static String getUserIdRole(String userName) {
119     if (sanitize) {
120       userName = SAFE_USERNAME.replaceFrom(userName, "_").toUpperCase();
121     }
122     return userRolePrefix.concat(userName);
123   }
124 
125   /**
126    * @see org.opencastproject.security.api.RoleProvider#getRolesForUser(String)
127    */
128   @Override
129   public List<Role> getRolesForUser(String userName) {
130     Organization organization = securityService.getOrganization();
131     List<Role> roles = new ArrayList<Role>();
132     roles.add(new JaxbRole(
133         getUserIdRole(userName),
134         JaxbOrganization.fromOrganization(organization),
135         "The user id role",
136         Role.Type.SYSTEM
137     ));
138     roles.add(new JaxbRole(
139         ROLE_USER,
140         JaxbOrganization.fromOrganization(organization),
141         "The authenticated user role",
142         Role.Type.SYSTEM
143     ));
144     return Collections.unmodifiableList(roles);
145   }
146 
147   /**
148    * @see org.opencastproject.security.api.RoleProvider#getOrganization()
149    */
150   @Override
151   public String getOrganization() {
152     return UserProvider.ALL_ORGANIZATIONS;
153   }
154 
155   /**
156    * @see org.opencastproject.security.api.RoleProvider#findRoles(String,Role.Target, int, int)
157    */
158   @Override
159   public Iterator<Role> findRoles(String query, Role.Target target, int offset, int limit) {
160     if (query == null) {
161       throw new IllegalArgumentException("Query must be set");
162     }
163 
164     // These roles are not meaningful for users/groups
165     if (target == Role.Target.USER) {
166       return Collections.emptyIterator();
167     }
168 
169     logger.debug("findRoles(query={} offset={} limit={})", query, offset, limit);
170 
171     HashSet<Role> foundRoles = new HashSet<Role>();
172     Organization organization = securityService.getOrganization();
173 
174     // Return authenticated user role if it matches the query pattern
175     if (like(ROLE_USER, query)) {
176       foundRoles.add(new JaxbRole(
177           ROLE_USER,
178           JaxbOrganization.fromOrganization(organization),
179           "The authenticated user role",
180           Role.Type.SYSTEM
181       ));
182     }
183 
184     // Include user id roles only if wildcard search or query matches user id role prefix
185     // (iterating through users may be slow)
186     if (!"%".equals(query) && !query.startsWith(userRolePrefix)) {
187       return foundRoles.iterator();
188     }
189 
190     String userQuery = "%";
191     if (query.startsWith(userRolePrefix)) {
192       userQuery = query.substring(userRolePrefix.length());
193     }
194 
195     Iterator<User> users = userDirectoryService.findUsers(userQuery, offset, limit);
196     while (users.hasNext()) {
197       User u = users.next();
198       // We exclude the digest user, but then add the global ROLE_USER above
199       if (!"system".equals(u.getProvider())) {
200         foundRoles.add(new JaxbRole(
201             getUserIdRole(u.getUsername()),
202             JaxbOrganization.fromOrganization(u.getOrganization()),
203             "User id role",
204             Role.Type.SYSTEM
205         ));
206       }
207     }
208 
209     return foundRoles.iterator();
210   }
211 
212   private static boolean like(String string, final String query) {
213     if (string == null) {
214       return false;
215     }
216     String regex = query.replace("_", ".").replace("%", ".*?");
217     Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
218     return p.matcher(string).matches();
219   }
220 
221   @Override
222   public void updated(Dictionary properties) throws ConfigurationException {
223     Optional<String> userPrefixProperty = OsgiUtil.getOptCfg(properties, ROLE_USER_PREFIX_KEY);
224     if (userPrefixProperty.isPresent()) {
225       userRolePrefix = userPrefixProperty.get();
226       logger.info("Using configured userRole prefix '{}'", userRolePrefix);
227     } else {
228       userRolePrefix = DEFAULT_ROLE_USER_PREFIX;
229       logger.info("Using default userRole prefix '{}'", userRolePrefix);
230     }
231 
232     Optional<String> sanitizeProperty = OsgiUtil.getOptCfg(properties, SANITIZE_KEY);
233     if (sanitizeProperty.isPresent()) {
234       sanitize = BooleanUtils.toBoolean(sanitizeProperty.get());
235       logger.info("Using configured will sanitize user names '{}'", sanitize);
236     } else {
237       sanitize = DEFAULT_SANITIZE;
238       logger.info("Using default for sanitizing user names '{}'", sanitize);
239     }
240   }
241 
242 }