1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.assetmanager.auth;
23
24 import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;
25
26 import org.opencastproject.security.api.Role;
27 import org.opencastproject.security.api.SecurityService;
28 import org.opencastproject.security.api.StaticFileAuthorization;
29 import org.opencastproject.security.api.User;
30
31 import org.apache.commons.lang3.BooleanUtils;
32 import org.osgi.service.component.ComponentContext;
33 import org.osgi.service.component.annotations.Activate;
34 import org.osgi.service.component.annotations.Component;
35 import org.osgi.service.component.annotations.Reference;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import java.sql.SQLSyntaxErrorException;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Dictionary;
43 import java.util.Hashtable;
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48 import java.util.stream.Collectors;
49
50 import javax.persistence.EntityManager;
51 import javax.persistence.EntityManagerFactory;
52 import javax.persistence.PersistenceException;
53 import javax.persistence.Query;
54
55
56
57
58 @Component(
59 property = {
60 "service.description=AssetManager based StaticFileAuthorization",
61 },
62 immediate = true,
63 service = StaticFileAuthorization.class
64 )
65 public class AssetManagerStaticFileAuthorization implements StaticFileAuthorization {
66
67 private static final Logger logger = LoggerFactory.getLogger(AssetManagerStaticFileAuthorization.class);
68
69 protected EntityManagerFactory entityManagerFactory;
70 private SecurityService securityService;
71
72 private Pattern staticFilePattern = Pattern.compile("^/([^/]+)/(?:api|internal)/([^/]+)/.*$");
73
74
75 private boolean includeAPIRoles = false;
76 private boolean includeCARoles = false;
77 private boolean includeUIRoles = false;
78
79 @Reference
80 public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
81 this.entityManagerFactory = entityManagerFactory;
82 }
83
84 @Reference
85 void setSecurityService(SecurityService securityService) {
86 this.securityService = securityService;
87 }
88
89 @Activate
90 public void activate(ComponentContext cc) {
91 List<Pattern> newPattern = new ArrayList<>();
92 Dictionary<String, Object> properties = cc != null ? cc.getProperties() : new Hashtable<>();
93 staticFilePattern = Pattern.compile(Objects.toString(
94 properties.get("pattern"),
95 "^/([^/]+)/(?:api|internal)/([^/]+)/.*$"));
96 includeAPIRoles = BooleanUtils.toBoolean(Objects.toString(properties.get("evaluate.roles.api"), null));
97 includeCARoles = BooleanUtils.toBoolean(Objects.toString(properties.get("evaluate.roles.ca"), null));
98 includeUIRoles = BooleanUtils.toBoolean(Objects.toString(properties.get("evaluate.roles.ui"), null));
99 logger.info("Started authentication handler for {}", staticFilePattern);
100 }
101
102 @Override
103 public List<Pattern> getProtectedUrlPattern() {
104 return Collections.singletonList(staticFilePattern);
105 }
106
107 @Override
108 public boolean verifyUrlAccess(final String path) {
109
110 final User user = securityService.getUser();
111 if (user.hasRole(GLOBAL_ADMIN_ROLE)) {
112 logger.debug("Allow access for admin `{}`", user);
113 return true;
114 }
115
116
117 final Matcher m = staticFilePattern.matcher(path);
118 if (!m.matches()) {
119 logger.debug("Path does not match pattern. Preventing access.");
120 return false;
121 }
122
123
124 final String organizationId = m.group(1);
125 if (!securityService.getOrganization().getId().equals(organizationId)) {
126 logger.debug("The user's organization does not match. Preventing access.");
127 return false;
128 }
129
130 if (user.getRoles().size() == 0) {
131 logger.debug("User has no roles allowing access.");
132 return false;
133 }
134
135
136
137
138
139
140
141
142
143
144
145 final List<String> roles = user.getRoles().parallelStream()
146 .map(Role::getName)
147 .filter(roleFilter)
148 .map((role) -> role + " | read")
149 .collect(Collectors.toList());
150
151 StringBuilder properties = new StringBuilder("property_name = ?");
152 for (int i = 1; i < roles.size(); i++) {
153 properties.append(" or property_name = ?");
154 }
155 String sql = "select count(1) from oc_assets_properties "
156 + "where val_bool = true "
157 + "and namespace = ? "
158 + "and mediapackage_id = ? "
159 + "and (" + properties + ")";
160 EntityManager entityManager = entityManagerFactory.createEntityManager();
161 Query q = entityManager.createNativeQuery(sql);
162 q.setParameter(1, "org.opencastproject.assetmanager.security");
163 q.setParameter(2, m.group(2));
164 for (int i = 0; i < roles.size(); i++) {
165 q.setParameter(i + 3, roles.get(i));
166 }
167 try {
168 return ((Long) q.getSingleResult()) > 0;
169 } catch (PersistenceException e) {
170 Throwable parent = e.getCause();
171 if (parent instanceof RuntimeException) {
172 parent = parent.getCause();
173 if (parent instanceof SQLSyntaxErrorException) {
174
175
176 logger.info("Denying access to static file {}. {}", path, parent.getMessage());
177 return false;
178 }
179 }
180 throw e;
181 }
182 }
183
184
185
186
187 private final java.util.function.Predicate<String> roleFilter = (name) -> (
188 includeAPIRoles || !name.startsWith("ROLE_API_"))
189 && (includeCARoles || !name.startsWith("ROLE_CAPTURE_AGENT_"))
190 && (includeUIRoles || !name.startsWith("ROLE_UI_"));
191 }