1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.opencastproject.userdirectory.brightspace;
24
25 import org.opencastproject.security.api.Organization;
26 import org.opencastproject.security.api.OrganizationDirectoryService;
27 import org.opencastproject.security.api.RoleProvider;
28 import org.opencastproject.security.api.SecurityConstants;
29 import org.opencastproject.security.api.UserProvider;
30 import org.opencastproject.userdirectory.brightspace.client.BrightspaceClientImpl;
31 import org.opencastproject.util.NotFoundException;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.math.NumberUtils;
35 import org.osgi.framework.BundleContext;
36 import org.osgi.framework.ServiceRegistration;
37 import org.osgi.service.cm.ConfigurationException;
38 import org.osgi.service.cm.ManagedServiceFactory;
39 import org.osgi.service.component.ComponentContext;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import java.net.URI;
47 import java.net.URISyntaxException;
48 import java.util.Dictionary;
49 import java.util.HashSet;
50 import java.util.Map;
51 import java.util.Set;
52 import java.util.concurrent.ConcurrentHashMap;
53
54 @Component(
55 immediate = true,
56 service = ManagedServiceFactory.class,
57 property = {
58 "service.pid=org.opencastproject.userdirectory.brightspace",
59 "service.description=Provides Brightspace user directory instances"
60 }
61 )
62 public class BrightspaceUserProviderFactory implements ManagedServiceFactory {
63
64 private static final Logger logger = LoggerFactory.getLogger(BrightspaceUserProviderFactory.class);
65
66 private static final String LTI_LEARNER_ROLE = "Learner";
67 private static final String LTI_INSTRUCTOR_ROLE = "Instructor";
68
69 private static final String ORGANIZATION_KEY = "org.opencastproject.userdirectory.brightspace.org";
70 private static final String BRIGHTSPACE_USER_ID = "org.opencastproject.userdirectory.brightspace.systemuser.id";
71 private static final String BRIGHTSPACE_USER_KEY = "org.opencastproject.userdirectory.brightspace.systemuser.key";
72 private static final String BRIGHTSPACE_URL = "org.opencastproject.userdirectory.brightspace.url";
73 private static final String BRIGHTSPACE_APP_ID = "org.opencastproject.userdirectory.brightspace.application.id";
74 private static final String BRIGHTSPACE_APP_KEY = "org.opencastproject.userdirectory.brightspace.application.key";
75
76 private static final String CACHE_SIZE_KEY = "org.opencastproject.userdirectory.brightspace.cache.size";
77 private static final String CACHE_EXPIRATION_KEY = "org.opencastproject.userdirectory.brightspace.cache.expiration";
78 private static final String BRIGHTSPACE_NAME = "org.opencastproject.userdirectory.brightspace";
79 private static final int DEFAULT_CACHE_SIZE_VALUE = 1000;
80 private static final int DEFAULT_CACHE_EXPIRATION_VALUE = 60;
81
82
83 private static final String BRIGHTSPACE_INSTRUCTOR_ROLES_KEY =
84 "org.opencastproject.userdirectory.brightspace.instructor.roles";
85 private static final String DEFAULT_BRIGHTSPACE_INSTRUCTOR_ROLES = "teacher,ta";
86
87 private static final String IGNORED_USERNAMES_KEY = "org.opencastproject.userdirectory.brightspace.ignored.usernames";
88 private static final String DEFAULT_IGNORED_USERNAMES = "admin,anonymous";
89
90 protected BundleContext bundleContext;
91 private Map<String, ServiceRegistration> providerRegistrations = new ConcurrentHashMap<>();
92 private OrganizationDirectoryService orgDirectory;
93 private int cacheSize;
94 private int cacheExpiration;
95
96
97
98
99 @Reference
100 public void setOrgDirectory(OrganizationDirectoryService orgDirectory) {
101 this.orgDirectory = orgDirectory;
102 }
103
104
105
106
107
108
109 @Activate
110 public void activate(ComponentContext cc) {
111 logger.debug("Activate BrightspaceUserProviderFactory");
112 this.bundleContext = cc.getBundleContext();
113 }
114
115
116
117
118
119
120 @Override
121 public String getName() {
122 return BRIGHTSPACE_NAME;
123 }
124
125
126
127
128
129
130 @Override
131 public void updated(String pid, Dictionary properties) throws ConfigurationException {
132 logger.debug("updated BrightspaceUserProviderFactory");
133 String adminUserName = StringUtils.trimToNull(
134 bundleContext.getProperty(SecurityConstants.GLOBAL_ADMIN_USER_PROPERTY));
135 String organization = (String) properties.get(ORGANIZATION_KEY);
136 String urlStr = (String) properties.get(BRIGHTSPACE_URL);
137 String systemUserId = (String) properties.get(BRIGHTSPACE_USER_ID);
138 String systemUserKey = (String) properties.get(BRIGHTSPACE_USER_KEY);
139 final String applicationId = (String) properties.get(BRIGHTSPACE_APP_ID);
140 final String applicationKey = (String) properties.get(BRIGHTSPACE_APP_KEY);
141
142 String cacheSizeStr = (String) properties.get(CACHE_SIZE_KEY);
143 if (StringUtils.isBlank(cacheSizeStr)) {
144 cacheSize = DEFAULT_CACHE_SIZE_VALUE;
145 } else {
146 cacheSize = NumberUtils.toInt(cacheSizeStr);
147 }
148
149
150 String cacheExpirationStr = (String) properties.get(CACHE_EXPIRATION_KEY);
151 if (StringUtils.isBlank(cacheExpirationStr)) {
152 cacheExpiration = DEFAULT_CACHE_EXPIRATION_VALUE;
153 } else {
154 cacheExpiration = NumberUtils.toInt(cacheExpirationStr);
155 }
156
157 String rolesStr = (String) properties.get(BRIGHTSPACE_INSTRUCTOR_ROLES_KEY);
158 if (StringUtils.isBlank(rolesStr)) {
159 rolesStr = DEFAULT_BRIGHTSPACE_INSTRUCTOR_ROLES;
160 }
161 Set instructorRoles = parsePropertyLineAsSet(rolesStr);
162 logger.debug("Brightspace instructor roles: {}", instructorRoles);
163
164 String ignoredUsersStr = (String) properties.get(IGNORED_USERNAMES_KEY);
165 if (StringUtils.isBlank(ignoredUsersStr)) {
166 ignoredUsersStr = DEFAULT_IGNORED_USERNAMES;
167 }
168 Set ignoredUsernames = parsePropertyLineAsSet(ignoredUsersStr);
169 logger.debug("Ignored users: {}", ignoredUsernames);
170
171 validateUrl(urlStr);
172 validateConfigurationKey(ORGANIZATION_KEY, organization);
173 validateConfigurationKey(BRIGHTSPACE_USER_ID, systemUserId);
174 validateConfigurationKey(BRIGHTSPACE_USER_KEY, systemUserKey);
175 validateConfigurationKey(BRIGHTSPACE_APP_ID, applicationId);
176 validateConfigurationKey(BRIGHTSPACE_APP_KEY, applicationKey);
177
178 ServiceRegistration existingRegistration = this.providerRegistrations.remove(pid);
179 if (existingRegistration != null) {
180 existingRegistration.unregister();
181 }
182
183 Organization org;
184 try {
185 org = this.orgDirectory.getOrganization(organization);
186 } catch (NotFoundException nfe) {
187 logger.warn("Organization {} not found!", organization);
188 throw new ConfigurationException(ORGANIZATION_KEY, "not found");
189 }
190
191 logger.debug("creating new brightspace user provider for pid={}", pid);
192
193 BrightspaceClientImpl clientImpl
194 = new BrightspaceClientImpl(urlStr, applicationId, applicationKey, systemUserId, systemUserKey);
195 BrightspaceUserProviderInstance provider
196 = new BrightspaceUserProviderInstance(pid, clientImpl, org, cacheSize, cacheExpiration ,
197 instructorRoles, ignoredUsernames);
198 this.providerRegistrations
199 .put(pid, this.bundleContext.registerService(UserProvider.class.getName(), provider, null));
200 this.providerRegistrations
201 .put(pid, this.bundleContext.registerService(RoleProvider.class.getName(), provider, null));
202 }
203
204
205
206
207
208
209 @Override
210 public void deleted(String pid) {
211 logger.debug("delete BrightspaceUserProviderInstance for pid={}", pid);
212 ServiceRegistration registration = providerRegistrations.remove(pid);
213 if (registration != null) {
214 registration.unregister();
215 }
216 }
217
218 private void validateConfigurationKey(String key, String value) throws ConfigurationException {
219 if (StringUtils.isBlank(value)) {
220 throw new ConfigurationException(key, "is not set");
221 }
222 }
223
224 private void validateUrl(String urlStr) throws ConfigurationException {
225 if (StringUtils.isBlank(urlStr)) {
226 throw new ConfigurationException(BRIGHTSPACE_URL, "is not set");
227 } else {
228 try {
229 new URI(urlStr);
230 } catch (URISyntaxException e) {
231 throw new ConfigurationException(BRIGHTSPACE_URL, "not a URL");
232 }
233 }
234 }
235
236 private Set<String> parsePropertyLineAsSet(String configLine) {
237 Set<String> set = new HashSet<>();
238 String[] configs = configLine.split(",");
239 for (String config: configs) {
240 set.add(config.trim());
241 }
242 return set;
243 }
244
245 }