Newer
Older
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com

srosse
committed
import static org.olat.restapi.security.RestSecurityHelper.getIdentity;
import static org.olat.restapi.security.RestSecurityHelper.getLocale;
import static org.olat.restapi.security.RestSecurityHelper.getRoles;
import static org.olat.restapi.security.RestSecurityHelper.getUserRequest;
import static org.olat.restapi.security.RestSecurityHelper.itself;
import static org.olat.user.restapi.UserVOFactory.formatDbUserProperty;
import static org.olat.user.restapi.UserVOFactory.get;

srosse
committed
import static org.olat.user.restapi.UserVOFactory.getManaged;
import static org.olat.user.restapi.UserVOFactory.parseUserProperty;
import static org.olat.user.restapi.UserVOFactory.post;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;

srosse
committed
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

srosse
committed
import javax.ws.rs.core.UriInfo;
import org.olat.basesecurity.Authentication;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.basesecurity.IdentityPowerSearchQueries;
import org.olat.basesecurity.OrganisationRoles;
import org.olat.basesecurity.OrganisationService;

srosse
committed
import org.olat.basesecurity.SearchIdentityParams;
import org.olat.basesecurity.model.IdentityRefImpl;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.persistence.DB;
import org.olat.core.gui.components.form.ValidationError;
import org.olat.core.gui.translator.PackageTranslator;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.id.Organisation;
import org.olat.core.id.Preferences;
import org.olat.core.id.Roles;
import org.olat.core.id.RolesByOrganisation;
import org.olat.core.id.User;
import org.olat.core.id.UserConstants;
import org.olat.core.util.StringHelper;

srosse
committed
import org.olat.restapi.group.MyGroupWebService;
import org.olat.restapi.support.MultipartReader;
import org.olat.restapi.support.vo.ErrorVO;
import org.olat.user.DisplayPortraitManager;
import org.olat.user.UserLifecycleManager;
import org.olat.user.UserManager;
import org.olat.user.propertyhandlers.UserPropertyHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* This web service handles functionalities related to <code>User</code>.
*
* @author srosse, stephane.rosse@frentix.com
*/
@Component
public class UserWebService {
private static final String VERSION = "1.0";
public static final String PROPERTY_HANDLER_IDENTIFIER = UserWebService.class.getName();
public static final CacheControl cc = new CacheControl();
@Autowired
private DB dbInstance;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurity securityManager;
@Autowired
private BaseSecurityModule securityModule;
@Autowired
private DisplayPortraitManager portraitManager;
@Autowired
private OrganisationService organisationService;
@Autowired
private UserLifecycleManager userLifecycleManager;
@Autowired
private IdentityPowerSearchQueries identitySearchQueries;
* @return The version number
*/
@GET
@Path("version")
@Operation(summary = "The version of the User Web Service", description = "The version of the User Web Service")
@ApiResponse(responseCode = "200", description = "The version of this specific Web Service")
@Produces(MediaType.TEXT_PLAIN)
public Response getVersion() {
return Response.ok(VERSION).build();
}
/**
* Search users and return them in a simple form (without user properties). User properties
* can be added two the query parameters. If the authUsername and the authProvider are set,
* the search is made only with these two parameters because they are sufficient to return
* a single user.<br>
* The search with login and user properties are made default with wild cards. If an exact
* match is needed, the parameter msut be quoted:<br>
* users?login="username"<br>
* Don't forget the right escaping in the URL!<br>
* You can make a search with the user properties like this:<br>
* users?telMobile=39847592&login=test
* <br >/ The lookup is possible for authors, usermanagers and system administrators. Normal
* users are not allowed to use the lookup service.
*
* @param login The login (search with like)
* @param authProvider An authentication provider (optional)
* @param authUsername An specific username from the authentication provider
* @param uriInfo The URI infos
* @param httpRequest The HTTP request
@Operation(summary = "Search users and return them in a simple form ", description = "Search users and return them in a simple form (without user properties). User properties\n" +
" can be added two the query parameters. If the authUsername and the authProvider are set,\n" +
" the search is made only with these two parameters because they are sufficient to return\n" +
" a single user.<br>\n" +
" The search with login and user properties are made default with wild cards. If an exact\n" +
" match is needed, the parameter msut be quoted:<br>\n" +
" users?login=\"username\"<br>\n" +
" Don't forget the right escaping in the URL!<br>\n" +
" You can make a search with the user properties like this:<br>\n" +
" users?telMobile=39847592&login=test\n" +
" <br >/ The lookup is possible for authors, usermanagers and system administrators. Normal\n" +
" users are not allowed to use the lookup service.")
@ApiResponse(responseCode = "200", description = "The list of all users in the OLAT system", content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = UserVO.class))),
@Content(mediaType = "application/xml", array = @ArraySchema(schema = @Schema(implementation = UserVO.class))) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getUserListQuery(@QueryParam("login") String login, @QueryParam("externalId") String externalId,
@QueryParam("authProvider") String authProvider, @QueryParam("authUsername") String authUsername,
@QueryParam("statusVisibleLimit") String statusVisibleLimit,
@Context UriInfo uriInfo, @Context HttpServletRequest httpRequest) {
// User lookup allowed for authors, usermanagers and admins. For
// usernamanger and up are considered "administrative" when it comes to
// lookup of the user properties
Roles roles = getRoles(httpRequest);
if(!roles.isAdministrator() && !roles.isUserManager() && !roles.isRolesManager() && !roles.isAuthor()) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
MultivaluedMap<String,String> params = uriInfo.getQueryParameters();
List<Identity> identities;
//make only a search by authUsername
if(StringHelper.containsNonWhitespace(authProvider) && StringHelper.containsNonWhitespace(authUsername)) {
Authentication auth = securityManager.findAuthenticationByAuthusername(authUsername, authProvider);
if(auth == null) {
identities = Collections.emptyList();
} else {
identities = Collections.singletonList(auth.getIdentity());
}
} else {
boolean isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles);
String[] authProviders = null;
if(StringHelper.containsNonWhitespace(authProvider)) {
authProviders = new String[]{authProvider};
}
//retrieve and convert the parameters value
Map<String,String> userProps = new HashMap<>();
if(!params.isEmpty()) {
Locale locale = getLocale(httpRequest);
List<UserPropertyHandler> propertyHandlers = userManager
.getUserPropertyHandlersFor(PROPERTY_HANDLER_IDENTIFIER, isAdministrativeUser);
for(UserPropertyHandler handler:propertyHandlers) {
if(!params.containsKey(handler.getName())) continue;
List<String> values = params.get(handler.getName());
if(!values.isEmpty()) {
String value = formatDbUserProperty(values.get(0), handler, locale);
userProps.put(handler.getName(), value);
}
Integer status = Identity.STATUS_VISIBLE_LIMIT;
if(isAdministrativeUser && "all".equalsIgnoreCase(statusVisibleLimit)) {
SearchIdentityParams searchParams = new SearchIdentityParams(login, userProps, true,
null, authProviders, null, null, null, null, status);
if(StringHelper.containsNonWhitespace(externalId)) {
searchParams.setExternalId(externalId);
}
identities = securityManager.getIdentitiesByPowerSearch(searchParams, 0, -1);
}
int count = 0;
UserVO[] userVOs = new UserVO[identities.size()];
for(Identity identity:identities) {
userVOs[count++] = get(identity);
}
return Response.ok(userVOs).build();
}

srosse
committed
@GET
@Path("managed")
@Operation(summary = "Get managed Users", description = "Get managed Users")
@ApiResponse(responseCode = "200", description = "List of all managed users", content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = ManagedUserVO.class))),
@Content(mediaType = "application/xml", array = @ArraySchema(schema = @Schema(implementation = ManagedUserVO.class))) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")

srosse
committed
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getManagedUsers(@Context HttpServletRequest httpRequest) {
if(!isUserManager(httpRequest)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
SearchIdentityParams params = new SearchIdentityParams();
params.setManaged(Boolean.TRUE);
List<Identity> identities = identitySearchQueries.getIdentitiesByPowerSearch(params, 0, -1);

srosse
committed
int count = 0;
ManagedUserVO[] userVOs = new ManagedUserVO[identities.size()];
for(Identity identity:identities) {
userVOs[count++] = getManaged(identity);
}
return Response.ok(userVOs).build();
}
* @param user The user to persist
* @param request The HTTP request
* @return the new persisted <code>User</code>
*/
@PUT
@Operation(summary = "Creates and persists a new user entity", description = "Creates and persists a new user entity")
@ApiResponse(responseCode = "200", description = "The persisted user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = UserVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = UserVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "406", description = "The list of errors", content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = ErrorVO.class))),
@Content(mediaType = "application/xml", array = @ArraySchema(schema = @Schema(implementation = ErrorVO.class))) })
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response create(UserVO user, @Context HttpServletRequest request) {
if(!isUserManager(request)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
if (!syntaxCheckOlatLogin(user)) {
Locale locale = getLocale(request);
Translator translator = Util.createPackageTranslator(UserShortDescription.class, locale);
String translation = translator.translate("new.error.loginname.empty");
ErrorVO[] errorVos = new ErrorVO[]{
new ErrorVO("org.olat.admin.user", "new.error.loginname.empty", translation)
};
return Response.ok(errorVos).status(Status.NOT_ACCEPTABLE).build();
}
Identity identity = securityManager.findIdentityByLogin(user.getLogin());
if (identity != null) {
Locale locale = getLocale(request);
Translator translator = Util.createPackageTranslator(UserShortDescription.class, locale);
String translation = translator.translate("new.error.loginname.choosen");
ErrorVO[] errorVos = new ErrorVO[]{
new ErrorVO("org.olat.admin.user", "new.error.loginname.choosen", translation)
};
return Response.ok(errorVos).status(Status.NOT_ACCEPTABLE).build();
}

srosse
committed
List<ErrorVO> errors = validateUser(null, user, request);
User newUser = userManager.createUser(user.getFirstName(), user.getLastName(), user.getEmail());
String identityName = securityModule.isIdentityNameAutoGenerated() ? null : user.getLogin();
Identity id = securityManager
.createAndPersistIdentityAndUserWithOrganisation(identityName, user.getLogin(), user.getExternalId(), newUser,
BaseSecurityModule.getDefaultAuthProviderIdentifier(), user.getLogin(), user.getPassword(), null);
userManager.updateUser(newUser);
return Response.ok(get(id)).build();
}
//content not ok
ErrorVO[] errorVos = new ErrorVO[errors.size()];
errors.toArray(errorVos);
return Response.ok(errorVos).status(Status.NOT_ACCEPTABLE).build();
}
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/**
* This a minimal syntax check specific to the REST API.
*
* @param user
* @return
*/
private boolean syntaxCheckOlatLogin(UserVO user) {
String login = user.getLogin();
if(!StringHelper.containsNonWhitespace(login)) {
return false;
}
char[] charArr = login.toCharArray();
for(char ch:charArr) {
if(ch == '/') {
return false;
}
if(ch < 32) {
return false;
}
}
if(login.indexOf('/') >= 0) {
return false;
}
return login.length() >= 3;
}
* Retrieves the roles of a user given its unique key identifier
* @param identityKey The user key identifier of the user being searched
* @param httpRequest The HTTP request
* @return an xml or json representation of a the roles being search.
@GET
@Path("{identityKey}/roles")
@Operation(summary = "Retrieve the roles", description = "Retrieves the roles of a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = RolesVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = RolesVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response getIdentityRoles(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
if(!isUserManagerOf(identityKey, request) && !itself(identityKey, request)) {
return Response.serverError().status(Status.FORBIDDEN).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
Roles roles = securityManager.getRoles(identity);
return Response.ok(new RolesVO(roles)).build();
/**
* Update the roles of a user given its unique key identifier
* @param identityKey The user key identifier of the user being searched
* @param roles The updated roles
* @param httpRequest The HTTP request
* @return an xml or json representation of a the roles being search.
*/
@Path("{identityKey}/roles")
@Operation(summary = "Update the roles", description = "Update the roles of a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = RolesVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = RolesVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@Consumes({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response updateRoles(@PathParam("identityKey") Long identityKey, RolesVO roles, @Context HttpServletRequest request) {
boolean isUserManager = isUserManagerOf(identityKey, request);
if(!isUserManager) {
return Response.serverError().status(Status.FORBIDDEN).build();
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Identity actingIdentity = getIdentity(request);
Organisation defOrganisation = organisationService.getDefaultOrganisation();
boolean userRole = !roles.isGuestOnly() && !roles.isInvitee();
RolesByOrganisation modifiedRoles = RolesByOrganisation.roles(defOrganisation,
roles.isGuestOnly(), roles.isInvitee(), userRole, roles.isAuthor(),
roles.isGroupManager(), roles.isPoolAdmin(), roles.isCurriculumManager(),
roles.isUserManager(), roles.isInstitutionalResourceManager(), roles.isOlatAdmin());
securityManager.updateRoles(actingIdentity, identity, modifiedRoles);
dbInstance.commit();// make sure all is committed before loading the roles again
Roles updatedRoles = securityManager.getRoles(identity);
return Response.ok(new RolesVO(updatedRoles)).build();

srosse
committed
/**
* Retrieves the status of a user given its unique key identifier

srosse
committed
* @param identityKey The user key identifier of the user being searched
* @param httpRequest The HTTP request
* @return an xml or json representation of a the status being search.
*/
@GET
@Path("{identityKey}/status")
@Operation(summary = "Retrieve the status", description = "Retrieves the status of a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = StatusVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = StatusVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")

srosse
committed
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response getStatus(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
boolean isUserManager = isUserManagerOf(identityKey, request);
if(!isUserManager) {
return Response.serverError().status(Status.FORBIDDEN).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();

srosse
committed
}
StatusVO status = new StatusVO();
status.setStatus(identity.getStatus());
return Response.ok(status).build();

srosse
committed
}
/**
* Update the roles of a user given its unique key identifier:
* <ul>
* <li>1: Permanent user</li>
* <li>2: activ</li>
* <li>101: login denied</li>
* <li>199: deleted</li>
* </ul>
*
* @param identityKey The user key identifier of the user being searched
* @param status The status to update
* @param httpRequest The HTTP request
* @return An xml or json representation of a the status after update.
*/
@POST
@Path("{identityKey}/status")
@Operation(summary = "Update the roles of a user", description = "Update the roles of a user given its unique key identifier:\n" +
" <ul>\n" +
" <li>1: Permanent user</li> \n" +
" <li>2: activ</li> \n" +
" <li>101: login denied</li> \n" +
" <li>199: deleted</li> \n" +
" </ul>\n" +
" ")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = StatusVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = StatusVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")

srosse
committed
@Consumes({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response updateStatus(@PathParam("identityKey") Long identityKey, StatusVO status, @Context HttpServletRequest request) {
Identity actingIdentity = getIdentity(request);
boolean isUserManager = isUserManagerOf(identityKey, request);
if(actingIdentity == null || !isUserManager) {
return Response.serverError().status(Status.FORBIDDEN).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();

srosse
committed
}
Integer newStatus = status.getStatus();
identity = securityManager.saveIdentityStatus(identity, newStatus, actingIdentity);
StatusVO reloadedStatus = new StatusVO();
reloadedStatus.setStatus(identity.getStatus());
return Response.ok(reloadedStatus).build();

srosse
committed
}
/**
* Retrieves the preferences of a user given its unique key identifier
* @param identityKey The user key identifier of the user being searched
* @param httpRequest The HTTP request
* @return an xml or json representation of a the roles being search.
*/
@GET
@Path("{identityKey}/preferences")
@Operation(summary = "Retrieve the preferences", description = "Retrieves the preferences of a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The preferences", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = PreferencesVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = PreferencesVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response getUserPreferences(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
boolean isUserManager = isUserManagerOf(identityKey, request);
if(!isUserManager) {
return Response.serverError().status(Status.FORBIDDEN).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Preferences prefs = identity.getUser().getPreferences();
return Response.ok(new PreferencesVO(prefs)).build();
}
/**
* Update the preferences of a user given its unique key identifier
* @param identityKey The user key identifier of the user being searched
* @param preferences The updated preferences
* @param httpRequest The HTTP request
* @return an xml or json representation of a the roles being search.
*/
@POST
@Path("{identityKey}/preferences")
@Operation(summary = "Update the preferences", description = "Update the preferences of a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = PreferencesVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = PreferencesVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@Consumes({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response updatePreferences(@PathParam("identityKey") Long identityKey, PreferencesVO preferences, @Context HttpServletRequest request) {
boolean isUserManager = isUserManagerOf(identityKey, request);
if(!isUserManager) {
return Response.serverError().status(Status.FORBIDDEN).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
Preferences prefs = identity.getUser().getPreferences();
prefs.setLanguage(preferences.getLanguage());
userManager.updateUserFromIdentity(identity);
return Response.ok(new PreferencesVO(prefs)).build();
@Path("{identityKey}/relations")
public IdentityToIdentityRelationsWebService getRelations(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
boolean isUserManager = isUserManagerOf(identityKey, request);
if(!isUserManager) {
throw new WebApplicationException(Status.FORBIDDEN);
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return new IdentityToIdentityRelationsWebService(identity);
}
/**
* Retrieves an user given its unique key identifier
* @param identityKey The user key identifier of the user being searched
* @param withPortrait If true return the portrait as Base64 (default false)
* @param httpRequest The HTTP request
* @return an xml or json representation of a the user being search. The xml
* correspond to a <code>UserVO</code>. <code>UserVO</code> is a
* simplified representation of the <code>User</code> and <code>Identity</code>
*/
@GET
@Path("{identityKey}")
@Operation(summary = "Retrieve a user", description = "Retrieves an user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = UserVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = UserVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@Produces({MediaType.APPLICATION_XML ,MediaType.APPLICATION_JSON})
public Response findById(@PathParam("identityKey") Long identityKey, @QueryParam("withPortrait") @DefaultValue("false") Boolean withPortrait,
@Context HttpServletRequest httpRequest) {
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
boolean isUserManager = isUserManagerOf(identityKey, httpRequest);
UserVO userVO = get(identity, null, true, isUserManager, withPortrait);
return Response.ok(userVO).build();

srosse
committed
@Path("{identityKey}/folders")
@Operation(summary = "Retrieve folders", description = "Retrieves folders from a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The folders")
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")

srosse
committed
public UserFoldersWebService getFoldersWebService(@PathParam("identityKey") Long identityKey) {
Identity identity = securityManager.loadIdentityByKey(identityKey, false);

srosse
committed
if(identity == null) {
throw new WebApplicationException(Response.serverError().status(Status.NOT_FOUND).build());
}
return new UserFoldersWebService(identity);
}
@Path("{identityKey}/courses")
@Operation(summary = "Retrieve courses", description = "Retrieves courses from a user given its unique key identifier")
@ApiResponse(responseCode = "200", description = "The courses")
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")

srosse
committed
public UserCoursesWebService getCoursesWebService(@PathParam("identityKey") Long identityKey,
@Context HttpServletRequest httpRequest) {
Identity identity = securityManager.loadIdentityByKey(identityKey, false);

srosse
committed
if(identity == null) {
throw new WebApplicationException(Response.serverError().status(Status.NOT_FOUND).build());
}
Identity ureqIdentity = getIdentity(httpRequest);
if(ureqIdentity == null || !ureqIdentity.equals(identity)) {
throw new WebApplicationException(Response.serverError().status(Status.UNAUTHORIZED).build());
}
UserCoursesWebService ws = new UserCoursesWebService(identity);
CoreSpringFactory.autowireObject(ws);
return ws;

srosse
committed
}

srosse
committed
* @param identityKey The identity key of the user being searched
* @return The image
*/
@HEAD
@Path("{identityKey}/portrait")
@Operation(summary = "Retrieves the portrait of an user", description = "Retrieves the portrait of an user")
@ApiResponse(responseCode = "200", description = "The portrait as image")
@ApiResponse(responseCode = "404", description = "The identity or the portrait not found")

srosse
committed
@Produces({"image/jpeg","image/jpg",MediaType.APPLICATION_OCTET_STREAM})
public Response getPortraitHead(@PathParam("identityKey") Long identityKey) {
Identity identity = securityManager.loadIdentityByKey(identityKey);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
File portrait = portraitManager.getBigPortrait(identity);
if(portrait == null || !portrait.exists()) {
return Response.serverError().status(Status.NOT_FOUND).build();

srosse
committed
}
Date lastModified = new Date(portrait.lastModified());
return Response.ok().lastModified(lastModified).build();

srosse
committed
}
/**
* Retrieves the portrait of an user
* @param identityKey The identity key of the user being searched
* @return The image
*/
@HEAD
@Path("{identityKey}/portrait/{size}")
@Operation(summary = "Retrieves the portrait of an user", description = "Retrieves the portrait of an user")
@ApiResponse(responseCode = "200", description = "The portrait as image")
@ApiResponse(responseCode = "404", description = "The identity or the portrait not found")
@Produces({"image/jpeg","image/jpg",MediaType.APPLICATION_OCTET_STREAM})
public Response getOriginalPortraitHead(@PathParam("identityKey") Long identityKey, @PathParam("size") String size) {
Identity identity = securityManager.loadIdentityByKey(identityKey);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
File portrait = null;
if("master".equals(size)) {
portrait = portraitManager.getMasterPortrait(identity);
} else if("big".equals(size)) {
portrait = portraitManager.getBigPortrait(identity);
} else if("small".equals(size)) {
portrait = portraitManager.getSmallPortrait(identity);
}
if(portrait == null || !portrait.exists()) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Date lastModified = new Date(portrait.lastModified());
return Response.ok().lastModified(lastModified).build();
}

srosse
committed
/**
* Retrieves the portrait of an user
* @param identityKey The identity key of the user being searched
* @param request The REST request
* @return The image
*/
@GET
@Path("{identityKey}/portrait")
@Operation(summary = "Retrieves the portrait of an user", description = "Retrieves the portrait of an user")
@ApiResponse(responseCode = "200", description = "The portrait as image")
@ApiResponse(responseCode = "404", description = "The identity or the portrait not found")
@Produces({"image/jpeg","image/jpg",MediaType.APPLICATION_OCTET_STREAM})
public Response getPortrait(@PathParam("identityKey") Long identityKey, @Context Request request) {
Identity identity = securityManager.loadIdentityByKey(identityKey);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
File portrait = portraitManager.getBigPortrait(identity);
if(portrait == null || !portrait.exists()) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Date lastModified = new Date(portrait.lastModified());
Response.ResponseBuilder response = request.evaluatePreconditions(lastModified);
if(response == null) {
response = Response.ok(portrait).lastModified(lastModified).cacheControl(cc);
return response.build();
* @param identityKey The user key identifier of the user being searched
* @param file The image
* @param request The REST request
* @return The image
*/
@POST
@Path("{identityKey}/portrait")
@Operation(summary = "Upload the portrait of an user", description = "Upload the portrait of an user")
@ApiResponse(responseCode = "200", description = "The portrait as image")
@ApiResponse(responseCode = "401", description = "Not authorized")
@ApiResponse(responseCode = "404", description = "The identity or the portrait not found")
public Response postPortrait(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
MultipartReader partsReader = null;
Identity identity = securityManager.loadIdentityByKey(identityKey);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Identity authIdentity = getUserRequest(request).getIdentity();
if(!isUserManagerOf(identityKey, request) && !identity.getKey().equals(authIdentity.getKey())) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
partsReader = new MultipartReader(request);
File tmpFile = partsReader.getFile();
String filename = partsReader.getFilename();
portraitManager.setPortrait(tmpFile, filename, identity);
} catch (Exception e) {
} finally {
MultipartReader.closeQuietly(partsReader);
}
}
/**
* Deletes the portrait of an user
* @param identityKey The identity key identifier of the user being searched
* @param request The REST request
* @return The image
*/
@DELETE
@Path("{identityKey}/portrait")
@Operation(summary = "Deletes the portrait of an user", description = "Deletes the portrait of an user")
@ApiResponse(responseCode = "200", description = "The portrait deleted")
@ApiResponse(responseCode = "401", description = "Not authorized")
public Response deletePortrait(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {
Identity authIdentity = getUserRequest(request).getIdentity();
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
} else if(!isUserManagerOf(identityKey, request) && !identity.equalsByPersistableKey(authIdentity)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
portraitManager.deletePortrait(identity);
return Response.ok().build();

srosse
committed
public MyGroupWebService getUserGroupList(@PathParam("identityKey") Long identityKey) {
Identity retrievedUser = securityManager.loadIdentityByKey(identityKey, false);

srosse
committed
return null;
MyGroupWebService ws = new MyGroupWebService(retrievedUser);
CoreSpringFactory.autowireObject(ws);
return ws;
* @param identityKey The user key identifier
* @param user The user datas
* @param request The HTTP request
* @return <code>User</code> object. The operation status (success or fail)
*/
@POST
@Path("{identityKey}")
@Operation(summary = "Update an user", description = "Update an user")
@ApiResponse(responseCode = "200", description = "The user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = UserVO.class)),
@Content(mediaType = "application/xml", schema = @Schema(implementation = UserVO.class)) })
@ApiResponse(responseCode = "401", description = "The roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@ApiResponse(responseCode = "406", description = "The list of validation errors", content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = ErrorVO.class))),
@Content(mediaType = "application/xml", array = @ArraySchema(schema = @Schema(implementation = ErrorVO.class))) })
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response update(@PathParam("identityKey") Long identityKey, UserVO user, @Context HttpServletRequest request) {
if(user == null) {
return Response.serverError().status(Status.NO_CONTENT).build();
}
if(!isUserManagerOf(identityKey, request)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
Identity retrievedIdentity = securityManager.loadIdentityByKey(identityKey, false);
if(retrievedIdentity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
User retrievedUser = retrievedIdentity.getUser();
List<ErrorVO> errors = validateUser(retrievedUser, user, request);
if(errors.isEmpty()) {
if(StringHelper.containsNonWhitespace(user.getExternalId())
&& !user.getExternalId().equals(retrievedIdentity.getExternalId())) {
retrievedIdentity = securityManager.setExternalId(retrievedIdentity, user.getExternalId());
retrievedUser = retrievedIdentity.getUser();
String oldEmail = retrievedUser.getEmail();
post(retrievedUser, user, getLocale(request));
userManager.updateUser(retrievedUser);
securityManager.deleteInvalidAuthenticationsByEmail(oldEmail);
return Response.ok(get(retrievedIdentity, true, true)).build();
//content not ok
ErrorVO[] errorVos = new ErrorVO[errors.size()];
errors.toArray(errorVos);
return Response.ok(errorVos).status(Status.NOT_ACCEPTABLE).build();

srosse
committed
private List<ErrorVO> validateUser(User user, UserVO userVo, HttpServletRequest request) {

uhensler
committed
List<ErrorVO> errors = new ArrayList<>();
List<UserPropertyHandler> propertyHandlers = userManager.getUserPropertyHandlersFor(PROPERTY_HANDLER_IDENTIFIER, false);
validateProperty(user, UserConstants.FIRSTNAME, userVo.getFirstName(), propertyHandlers, errors, userManager, locale);
validateProperty(user, UserConstants.LASTNAME, userVo.getLastName(), propertyHandlers, errors, userManager, locale);
validateProperty(user, UserConstants.EMAIL, userVo.getEmail(), propertyHandlers, errors, userManager, locale);
for (UserPropertyHandler propertyHandler : propertyHandlers) {
if(!UserConstants.FIRSTNAME.equals(propertyHandler.getName())
&& !UserConstants.LASTNAME.equals(propertyHandler.getName())
&& !UserConstants.EMAIL.equals(propertyHandler.getName())) {
validateProperty(user, userVo, propertyHandler, errors, userManager, locale);

srosse
committed
private boolean validateProperty(User user, String name, String value, List<UserPropertyHandler> handlers, List<ErrorVO> errors, UserManager um, Locale locale) {
for(UserPropertyHandler handler:handlers) {
if(handler.getName().equals(name)) {

srosse
committed
return validateProperty(user, value, handler, errors, um, locale);

srosse
committed
private boolean validateProperty(User user, UserVO userVo, UserPropertyHandler userPropertyHandler, List<ErrorVO> errors, UserManager um, Locale locale) {
String value = userVo.getProperty(userPropertyHandler.getName());
return validateProperty(user, value, userPropertyHandler, errors, um, locale);

srosse
committed
private boolean validateProperty(User user, String value, UserPropertyHandler userPropertyHandler, List<ErrorVO> errors, UserManager um, Locale locale) {
ValidationError error = new ValidationError();
if(!StringHelper.containsNonWhitespace(value) && um.isMandatoryUserProperty(PROPERTY_HANDLER_IDENTIFIER, userPropertyHandler)) {
Translator translator = new PackageTranslator("org.olat.core", locale);
String translation = translator.translate("new.form.mandatory");

srosse
committed
errors.add(new ErrorVO("org.olat.core:new.form.mandatory:" + userPropertyHandler.getName(), translation));
return false;
}
value = parseUserProperty(value, userPropertyHandler, locale);

srosse
committed
if (!userPropertyHandler.isValidValue(user, value, error, locale)) {
String pack = userPropertyHandler.getClass().getPackage().getName();
Translator translator = new PackageTranslator(pack, locale);

srosse
committed
String translation = translator.translate(error.getErrorKey(), error.getArgs());
errors.add(new ErrorVO(pack, error.getErrorKey(), translation));
return false;

uhensler
committed
} else if((userPropertyHandler.getName().equals(UserConstants.INSTITUTIONALEMAIL) && StringHelper.containsNonWhitespace(value))
|| userPropertyHandler.getName().equals(UserConstants.EMAIL)) {
if (!userManager.isEmailAllowed(value, user)) {
String pack = userPropertyHandler.getClass().getPackage().getName();
Translator translator = new PackageTranslator(pack, locale);
String translation = translator.translate("form.name." + userPropertyHandler.getName() + ".error.exists", new String[] { value });
translation += " (" + value + ")";
errors.add(new ErrorVO("org.olat.user.propertyhandlers:new.form.name." + userPropertyHandler.getName() + ".exists", translation));
}
}
return true;
}
/**
* Delete an user from the system
* @param identityKey The user key identifier
* @param request The HTTP request
* @return <code>Response</code> object. The operation status (success or fail)
*/
@DELETE
@Path("{identityKey}")
@Operation(summary = "Delete an user from the system", description = "Delete an user from the system")
@ApiResponse(responseCode = "200", description = "The user is removed from the group")
@ApiResponse(responseCode = "401", description = "he roles of the authenticated user are not sufficient")
@ApiResponse(responseCode = "404", description = "The identity not found")
@ApiResponse(responseCode = "500", description = "Unknown problem while deleting, see olat.log")
public Response delete(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest request) {

srosse
committed
Identity actingIdentity = getIdentity(request);
if(actingIdentity == null || !isUserManagerOf(identityKey, request)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
Identity identity = securityManager.loadIdentityByKey(identityKey, false);
if(identity == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
boolean success = userLifecycleManager.deleteIdentity(identity, actingIdentity);
if (success) {
return Response.ok().build();
} else {
return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
}
private boolean isUserManager(HttpServletRequest request) {
Roles managerRoles = getRoles(request);
return managerRoles.isUserManager() || managerRoles.isRolesManager() || managerRoles.isAdministrator();
}
private boolean isUserManagerOf(Long identityKey, HttpServletRequest request) {
if(identityKey == null) return false;
Roles managerRoles = getRoles(request);
if(!managerRoles.isUserManager() && !managerRoles.isRolesManager() && !managerRoles.isAdministrator()) {
return false;