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.userdirectory.endpoint;
23
24 import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
25 import static org.apache.http.HttpStatus.SC_CONFLICT;
26 import static org.apache.http.HttpStatus.SC_CREATED;
27 import static org.apache.http.HttpStatus.SC_FORBIDDEN;
28 import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
29 import static org.apache.http.HttpStatus.SC_NOT_FOUND;
30 import static org.apache.http.HttpStatus.SC_OK;
31 import static org.opencastproject.util.RestUtil.getEndpointUrl;
32 import static org.opencastproject.util.UrlSupport.uri;
33 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
34
35 import org.opencastproject.security.api.JaxbUser;
36 import org.opencastproject.security.api.JaxbUserList;
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.impl.jpa.JpaOrganization;
41 import org.opencastproject.security.impl.jpa.JpaRole;
42 import org.opencastproject.security.impl.jpa.JpaUser;
43 import org.opencastproject.userdirectory.JpaUserAndRoleProvider;
44 import org.opencastproject.util.NotFoundException;
45 import org.opencastproject.util.UrlSupport;
46 import org.opencastproject.util.data.Tuple;
47 import org.opencastproject.util.doc.rest.RestParameter;
48 import org.opencastproject.util.doc.rest.RestQuery;
49 import org.opencastproject.util.doc.rest.RestResponse;
50 import org.opencastproject.util.doc.rest.RestService;
51
52 import org.apache.commons.lang3.StringUtils;
53 import org.json.simple.JSONArray;
54 import org.json.simple.JSONValue;
55 import org.osgi.service.component.ComponentContext;
56 import org.osgi.service.component.annotations.Component;
57 import org.osgi.service.component.annotations.Reference;
58 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import java.io.IOException;
63 import java.util.HashSet;
64 import java.util.Iterator;
65 import java.util.Set;
66
67 import javax.ws.rs.DELETE;
68 import javax.ws.rs.FormParam;
69 import javax.ws.rs.GET;
70 import javax.ws.rs.POST;
71 import javax.ws.rs.PUT;
72 import javax.ws.rs.Path;
73 import javax.ws.rs.PathParam;
74 import javax.ws.rs.Produces;
75 import javax.ws.rs.QueryParam;
76 import javax.ws.rs.core.MediaType;
77 import javax.ws.rs.core.Response;
78
79
80
81
82 @Path("/user-utils")
83 @RestService(
84 name = "UsersUtils",
85 title = "User utils",
86 notes = "This service offers the default CRUD Operations for the internal Opencast users.",
87 abstractText = "Provides operations for internal Opencast users")
88 @Component(
89 property = {
90 "service.description=User REST endpoint",
91 "opencast.service.type=org.opencastproject.userdirectory.endpoint.UserEndpoint",
92 "opencast.service.path=/user-utils",
93 "opencast.service.jobproducer=false"
94 },
95 immediate = true,
96 service = { UserEndpoint.class }
97 )
98 @JaxrsResource
99 public class UserEndpoint {
100
101
102 private static final Logger logger = LoggerFactory.getLogger(UserEndpoint.class);
103
104 private JpaUserAndRoleProvider jpaUserAndRoleProvider;
105
106 private SecurityService securityService;
107
108 private String endpointBaseUrl;
109
110
111 public void activate(ComponentContext cc) {
112 logger.info("Start users endpoint");
113 final Tuple<String, String> endpointUrl = getEndpointUrl(cc);
114 endpointBaseUrl = UrlSupport.concat(endpointUrl.getA(), endpointUrl.getB());
115 }
116
117
118
119
120
121 @Reference
122 public void setSecurityService(SecurityService securityService) {
123 this.securityService = securityService;
124 }
125
126
127
128
129
130 @Reference
131 public void setJpaUserAndRoleProvider(JpaUserAndRoleProvider jpaUserAndRoleProvider) {
132 this.jpaUserAndRoleProvider = jpaUserAndRoleProvider;
133 }
134
135 @GET
136 @Path("users.json")
137 @Produces(MediaType.APPLICATION_JSON)
138 @RestQuery(
139 name = "allusersasjson",
140 description = "Returns a list of users",
141 returnDescription = "Returns a JSON representation of the list of user accounts",
142 restParameters = {
143 @RestParameter(
144 name = "limit",
145 defaultValue = "100",
146 description = "The maximum number of items to return per page.",
147 isRequired = false,
148 type = RestParameter.Type.STRING),
149 @RestParameter(
150 name = "offset",
151 defaultValue = "0",
152 description = "The page number.",
153 isRequired = false,
154 type = RestParameter.Type.STRING)
155 }, responses = {
156 @RestResponse(
157 responseCode = SC_OK,
158 description = "The user accounts.")
159 })
160 public JaxbUserList getUsersAsJson(@QueryParam("limit") int limit, @QueryParam("offset") int offset)
161 throws IOException {
162
163
164 if (limit < 1) {
165 limit = 100;
166 }
167
168 JaxbUserList userList = new JaxbUserList();
169 for (Iterator<User> i = jpaUserAndRoleProvider.findUsers("%", offset, limit); i.hasNext();) {
170 userList.add(i.next());
171 }
172 return userList;
173 }
174
175 @GET
176 @Path("{username}.json")
177 @Produces(MediaType.APPLICATION_JSON)
178 @RestQuery(
179 name = "user",
180 description = "Returns a user",
181 returnDescription = "Returns a JSON representation of a user",
182 pathParameters = {
183 @RestParameter(
184 name = "username",
185 description = "The username.",
186 isRequired = true,
187 type = STRING)
188 }, responses = {
189 @RestResponse(
190 responseCode = SC_OK,
191 description = "The user account."),
192 @RestResponse(
193 responseCode = SC_NOT_FOUND,
194 description = "User not found")
195 })
196 public Response getUserAsJson(@PathParam("username") String username) throws NotFoundException {
197 User user = jpaUserAndRoleProvider.loadUser(username);
198 if (user == null) {
199 logger.debug("Requested user not found: {}", username);
200 return Response.status(SC_NOT_FOUND).build();
201 }
202 return Response.ok(JaxbUser.fromUser(user)).build();
203 }
204
205 @GET
206 @Path("users/md5.json")
207 @Produces(MediaType.APPLICATION_JSON)
208 @RestQuery(
209 name = "users-with-insecure-hashing",
210 description = "Returns a list of users which passwords are stored using MD5 hashes",
211 returnDescription = "Returns a JSON representation of the list of matching user accounts",
212 responses = {
213 @RestResponse(
214 responseCode = SC_OK,
215 description = "The user accounts.")
216 })
217 public JaxbUserList getUserWithInsecurePasswordHashingAsJson() {
218 JaxbUserList userList = new JaxbUserList();
219 for (User user: jpaUserAndRoleProvider.findInsecurePasswordHashes()) {
220 userList.add(user);
221 }
222 return userList;
223 }
224
225 @POST
226 @Path("/")
227 @RestQuery(
228 name = "createUser",
229 description = "Create a new user",
230 returnDescription = "Location of the new ressource",
231 restParameters = {
232 @RestParameter(
233 name = "username",
234 description = "The username.",
235 isRequired = true,
236 type = STRING),
237 @RestParameter(
238 name = "password",
239 description = "The password.",
240 isRequired = true,
241 type = STRING),
242 @RestParameter(
243 name = "name",
244 description = "The name.",
245 isRequired = false,
246 type = STRING),
247 @RestParameter(
248 name = "email",
249 description = "The email.",
250 isRequired = false,
251 type = STRING),
252 @RestParameter(
253 name = "roles",
254 description = "The user roles as a json array, for example: [\"ROLE_USER\", \"ROLE_ADMIN\"]",
255 isRequired = false,
256 type = STRING)
257 }, responses = {
258 @RestResponse(
259 responseCode = SC_BAD_REQUEST,
260 description = "Malformed request syntax."),
261 @RestResponse(
262 responseCode = SC_CREATED,
263 description = "User has been created."),
264 @RestResponse(
265 responseCode = SC_CONFLICT,
266 description = "An user with this username already exist."),
267 @RestResponse(
268 responseCode = SC_FORBIDDEN,
269 description = "Not enough permissions to create a user with the admin role.")
270 })
271 public Response createUser(@FormParam("username") String username, @FormParam("password") String password,
272 @FormParam("name") String name, @FormParam("email") String email, @FormParam("roles") String roles) {
273
274 if (jpaUserAndRoleProvider.loadUser(username) != null) {
275 return Response.status(SC_CONFLICT).build();
276 }
277
278 try {
279 Set<JpaRole> rolesSet = parseRoles(roles);
280
281
282 logger.debug("Updating user {}", username);
283 JpaOrganization organization = (JpaOrganization) securityService.getOrganization();
284 JpaUser user = new JpaUser(username, password, organization, name, email, jpaUserAndRoleProvider.getName(), true,
285 rolesSet);
286 try {
287 jpaUserAndRoleProvider.addUser(user);
288 return Response.created(uri(endpointBaseUrl, user.getUsername() + ".json")).build();
289 } catch (UnauthorizedException ex) {
290 logger.debug("Create user failed", ex);
291 return Response.status(Response.Status.FORBIDDEN).build();
292 }
293
294 } catch (IllegalArgumentException e) {
295 logger.debug("Request with malformed ROLE data: {}", roles);
296 return Response.status(SC_BAD_REQUEST).build();
297 }
298 }
299
300 @PUT
301 @Path("{username}.json")
302 @RestQuery(
303 name = "updateUser",
304 description = "Update an user",
305 returnDescription = "Status ok",
306 restParameters = {
307 @RestParameter(
308 name = "password",
309 description = "The password.",
310 isRequired = true,
311 type = STRING),
312 @RestParameter(
313 name = "name",
314 description = "The name.",
315 isRequired = false,
316 type = STRING),
317 @RestParameter(
318 name = "email",
319 description = "The email.",
320 isRequired = false,
321 type = STRING),
322 @RestParameter(
323 name = "roles",
324 description = "The user roles as a json array, for example: [\"ROLE_USER\", \"ROLE_ADMIN\"]",
325 isRequired = false,
326 type = STRING)
327 }, pathParameters = @RestParameter(
328 name = "username",
329 description = "The username",
330 isRequired = true,
331 type = STRING),
332 responses = {
333 @RestResponse(
334 responseCode = SC_BAD_REQUEST,
335 description = "Malformed request syntax."),
336 @RestResponse(
337 responseCode = SC_FORBIDDEN,
338 description = "Not enough permissions to update a user with the admin role."),
339 @RestResponse(
340 responseCode = SC_OK,
341 description = "User has been updated.") })
342 public Response setUser(@PathParam("username") String username, @FormParam("password") String password,
343 @FormParam("name") String name, @FormParam("email") String email, @FormParam("roles") String roles) {
344
345 try {
346 User user = jpaUserAndRoleProvider.loadUser(username);
347 if (user == null) {
348 return createUser(username, password, name, email, roles);
349 }
350
351 Set<JpaRole> rolesSet = parseRoles(roles);
352
353 logger.debug("Updating user {}", username);
354 JpaOrganization organization = (JpaOrganization) securityService.getOrganization();
355 jpaUserAndRoleProvider.updateUser(new JpaUser(username, password, organization, name, email,
356 jpaUserAndRoleProvider.getName(), true, rolesSet));
357 return Response.status(SC_OK).build();
358 } catch (NotFoundException e) {
359 logger.debug("User {} not found.", username);
360 return Response.status(SC_NOT_FOUND).build();
361 } catch (UnauthorizedException e) {
362 logger.debug("Update user failed", e);
363 return Response.status(Response.Status.FORBIDDEN).build();
364 } catch (IllegalArgumentException e) {
365 logger.debug("Request with malformed ROLE data: {}", roles);
366 return Response.status(SC_BAD_REQUEST).build();
367 }
368 }
369
370 @DELETE
371 @Path("{username}.json")
372 @RestQuery(
373 name = "deleteUser",
374 description = "Delete a new user",
375 returnDescription = "Status ok",
376 pathParameters = @RestParameter(
377 name = "username",
378 type = STRING,
379 isRequired = true,
380 description = "The username"),
381 responses = {
382 @RestResponse(
383 responseCode = SC_OK,
384 description = "User has been deleted."),
385 @RestResponse(
386 responseCode = SC_FORBIDDEN,
387 description = "Not enough permissions to delete a user with the admin role."),
388 @RestResponse(
389 responseCode = SC_NOT_FOUND,
390 description = "User not found.")
391 })
392 public Response deleteUser(@PathParam("username") String username) {
393 try {
394 jpaUserAndRoleProvider.deleteUser(username, securityService.getOrganization().getId());
395 } catch (NotFoundException e) {
396 logger.debug("User {} not found.", username);
397 return Response.status(SC_NOT_FOUND).build();
398 } catch (UnauthorizedException e) {
399 logger.debug("Error during deletion of user {}", username, e);
400 return Response.status(SC_FORBIDDEN).build();
401 } catch (Exception e) {
402 logger.error("Error during deletion of user {}", username, e);
403 return Response.status(SC_INTERNAL_SERVER_ERROR).build();
404 }
405
406 logger.debug("User {} removed.", username);
407 return Response.status(SC_OK).build();
408 }
409
410
411
412
413
414
415
416 private Set<JpaRole> parseRoles(String roles) throws IllegalArgumentException {
417 JSONArray rolesArray = null;
418
419 try {
420 rolesArray = (JSONArray) JSONValue.parseWithException(StringUtils.isEmpty(roles) ? "[]" : roles);
421 } catch (Exception e) {
422 throw new IllegalArgumentException("Error parsing JSON array", e);
423 }
424
425 Set<JpaRole> rolesSet = new HashSet<JpaRole>();
426
427 for (Object role : rolesArray) {
428 try {
429 rolesSet.add(new JpaRole((String) role, (JpaOrganization) securityService.getOrganization()));
430 } catch (ClassCastException e) {
431 throw new IllegalArgumentException("Error parsing array vales as String", e);
432 }
433 }
434
435 return rolesSet;
436 }
437
438 }