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.security.util;
23  
24  import static org.apache.commons.lang3.StringUtils.isBlank;
25  import static org.opencastproject.security.api.SecurityConstants.EPISODE_ROLE_ID_PREFIX;
26  import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;
27  import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ANONYMOUS_USERNAME;
28  import static org.opencastproject.security.api.SecurityConstants.GLOBAL_CAPTURE_AGENT_ROLE;
29  import static org.opencastproject.util.data.Tuple.tuple;
30  
31  import org.opencastproject.security.api.JaxbOrganization;
32  import org.opencastproject.security.api.JaxbRole;
33  import org.opencastproject.security.api.JaxbUser;
34  import org.opencastproject.security.api.Organization;
35  import org.opencastproject.security.api.OrganizationDirectoryService;
36  import org.opencastproject.security.api.SecurityConstants;
37  import org.opencastproject.security.api.SecurityService;
38  import org.opencastproject.security.api.UnauthorizedException;
39  import org.opencastproject.security.api.User;
40  import org.opencastproject.security.api.UserDirectoryService;
41  import org.opencastproject.util.ConfigurationException;
42  import org.opencastproject.util.NotFoundException;
43  import org.opencastproject.util.data.Tuple;
44  
45  import org.apache.commons.lang3.StringUtils;
46  import org.osgi.service.component.ComponentContext;
47  
48  import java.net.URL;
49  import java.util.Collections;
50  import java.util.HashMap;
51  import java.util.Map;
52  import java.util.Optional;
53  import java.util.regex.Pattern;
54  
55  /** Opencast security helpers. */
56  public final class SecurityUtil {
57    private static final Pattern SANITIZING_PATTERN = Pattern.compile("[^a-zA-Z0-9_]");
58  
59    private SecurityUtil() {
60    }
61  
62    /** The name of the key used to store the name of the system user in the global config. */
63    public static final String PROPERTY_KEY_SYS_USER = "org.opencastproject.security.digest.user";
64  
65    public static final String PROPERTY_KEY_ACL_ADDITIONAL_ACTIONS = "org.opencastproject.acl.additional.actions.";
66  
67    /**
68     * Run function <code>f</code> in the context described by the given organization and user.
69     *
70     * @param sec
71     *          Security service to use for getting data
72     * @param org
73     *          Organization to switch to
74     * @param user
75     *          User to switch to
76     * @param fn
77     *          Function to execute
78     */
79    public static void runAs(SecurityService sec, Organization org, User user, Runnable fn) {
80      final Organization prevOrg = sec.getOrganization();
81      final User prevUser = prevOrg != null ? sec.getUser() : null;
82      sec.setOrganization(org);
83      sec.setUser(user);
84      try {
85        fn.run();
86      } finally {
87        sec.setOrganization(prevOrg);
88        sec.setUser(prevUser);
89      }
90    }
91  
92    /**
93     * Create a system user for the given organization with global and organization local admin role. Get the
94     * <code>systemUserName</code> from the global config where it is stored under {@link #PROPERTY_KEY_SYS_USER}. In an
95     * OSGi environment this is typically done calling
96     * <code>componentContext.getBundleContext().getProperty(PROPERTY_KEY_SYS_USER)</code>.
97     *
98     * @see #createSystemUser(org.osgi.service.component.ComponentContext, org.opencastproject.security.api.Organization)
99     */
100   public static User createSystemUser(String systemUserName, Organization org) {
101     JaxbOrganization jaxbOrganization = JaxbOrganization.fromOrganization(org);
102     return new JaxbUser(systemUserName, null, jaxbOrganization, new JaxbRole(GLOBAL_ADMIN_ROLE, jaxbOrganization),
103             new JaxbRole(org.getAdminRole(), jaxbOrganization));
104   }
105 
106   /**
107    * Create the global anonymous user with the given organization.
108    *
109    * @param org
110    *          the organization
111    * @return the global anonymous user
112    */
113   public static User createAnonymousUser(Organization org) {
114     JaxbOrganization jaxbOrganization = JaxbOrganization.fromOrganization(org);
115     return new JaxbUser(GLOBAL_ANONYMOUS_USERNAME, null, jaxbOrganization, new JaxbRole(
116             jaxbOrganization.getAnonymousRole(), jaxbOrganization));
117   }
118 
119   /**
120    * Create a system user for the given organization with global admin role. The system user name is fetched from the
121    * global OSGi config.
122    *
123    * @see #createSystemUser(String, org.opencastproject.security.api.Organization)
124    */
125   public static User createSystemUser(ComponentContext cc, Organization org) {
126     final String systemUserName = cc.getBundleContext().getProperty(PROPERTY_KEY_SYS_USER);
127     return createSystemUser(systemUserName, org);
128   }
129 
130   /**
131    * Fetch the system user name from the configuration.
132    *
133    * @see #PROPERTY_KEY_SYS_USER
134    */
135   public static String getSystemUserName(ComponentContext cc) {
136     final String systemUserName = cc.getBundleContext().getProperty(PROPERTY_KEY_SYS_USER);
137     if (systemUserName != null) {
138       return systemUserName;
139     } else {
140       throw new ConfigurationException(
141               "An Opencast installation always needs a system user name. Please configure one under the key "
142                       + PROPERTY_KEY_SYS_USER);
143     }
144   }
145 
146   /**
147    * Get a user and an organization. Only returns something if both elements can be determined.
148    */
149   public static Optional<Tuple<User, Organization>> getUserAndOrganization(SecurityService sec,
150           OrganizationDirectoryService orgDir, String orgId, UserDirectoryService userDir, String userId) {
151     final Organization prevOrg = sec.getOrganization();
152     try {
153       final Organization org = orgDir.getOrganization(orgId);
154       sec.setOrganization(org);
155       return Optional.ofNullable(userDir.loadUser(userId))
156               .map(user -> tuple(user, org));
157     } catch (NotFoundException e) {
158       return Optional.empty();
159     } finally {
160       sec.setOrganization(prevOrg);
161     }
162   }
163 
164   /** Extract hostname and port number from a URL. */
165   public static Tuple<String, Integer> hostAndPort(URL url) {
166     int port = url.getPort();
167     if (port < 0) {
168       port = url.getDefaultPort();
169     }
170     return tuple(StringUtils.strip(url.getHost(), "/"), port);
171   }
172 
173   /**
174    * Check if the current user has access to the capture agent with the given id.
175    * @param agentId
176    *           The agent id to check.
177    * @throws UnauthorizedException
178    *           If the user doesn't have access.
179    */
180   public static void checkAgentAccess(final SecurityService securityService, final String agentId)
181           throws UnauthorizedException {
182     if (isBlank(agentId)) {
183       return;
184     }
185     final User user = securityService.getUser();
186     if (user.hasRole(SecurityConstants.GLOBAL_ADMIN_ROLE) || user.hasRole(user.getOrganization().getAdminRole())) {
187       return;
188     }
189     if (!user.hasRole(SecurityUtil.getCaptureAgentRole(agentId))) {
190       throw new UnauthorizedException(user, "schedule");
191     }
192   }
193 
194   /**
195    * Get additional ACL actions defined in the organization properties.
196    *
197    * @param organization
198    *          The organization to get the additional actions from
199    * @return A map of additional actions
200    */
201   public static Map<String, String> additionalAclActions(final Organization organization) {
202     Map<String, String> additionalActions = new HashMap<>();
203     organization.getProperties().forEach((key, value) -> {
204       if (key.startsWith(PROPERTY_KEY_ACL_ADDITIONAL_ACTIONS)) {
205         additionalActions.put(key.substring(PROPERTY_KEY_ACL_ADDITIONAL_ACTIONS.length()), value);
206       }
207     });
208     return Collections.unmodifiableMap(additionalActions);
209   }
210 
211   private static String sanitizeCaName(final String ca) {
212     return SANITIZING_PATTERN.matcher(ca).replaceAll("").toUpperCase();
213   }
214 
215   /**
216    * Get the role name of the role required to access the capture agent with the given agent id.
217    *
218    * @param
219    *     agentId The id of the agent to get the role for.
220    * @return
221    *     The role name.
222    */
223   public static String getCaptureAgentRole(final String agentId) {
224     return GLOBAL_CAPTURE_AGENT_ROLE + "_" + sanitizeCaName(agentId);
225   }
226 
227   /**
228    * Get the episode role id for a mediapackage and an action
229    *
230    * @param mediaPackageId the id of the mediapackage
231    * @param action the action as a string
232    * @return the role
233    */
234   public static String getEpisodeRoleId(final String mediaPackageId, final String action) {
235     return EPISODE_ROLE_ID_PREFIX + "_" + mediaPackageId + "_" + action.toUpperCase();
236   }
237 }