/** * <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 * <p> */ package org.olat.restapi.repository.course; import static org.olat.restapi.security.RestSecurityHelper.getIdentity; import static org.olat.restapi.security.RestSecurityHelper.getUserRequest; import static org.olat.restapi.security.RestSecurityHelper.isAdmin; import static org.olat.restapi.security.RestSecurityHelper.isAuthor; import static org.olat.restapi.security.RestSecurityHelper.isAuthorEditor; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; 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.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.olat.admin.securitygroup.gui.IdentitiesAddEvent; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.SecurityGroup; import org.olat.commons.calendar.CalendarModule; import org.olat.commons.calendar.restapi.CalWebService; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.media.MediaResource; import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LearningResourceLoggingAction; import org.olat.core.logging.activity.OlatResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.mail.MailPackage; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.xml.XStreamHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.config.CourseConfig; import org.olat.course.nodes.cal.CourseCalendars; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.modules.gotomeeting.restapi.GoToTrainingWebService; import org.olat.modules.vitero.restapi.ViteroBookingWebService; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.resource.accesscontrol.ACService; import org.olat.resource.accesscontrol.AccessResult; import org.olat.restapi.security.RestSecurityHelper; import org.olat.restapi.support.ObjectFactory; import org.olat.restapi.support.vo.CourseConfigVO; import org.olat.restapi.support.vo.CourseVO; import org.olat.restapi.support.vo.OlatResourceVO; import org.olat.user.restapi.UserVO; import org.olat.user.restapi.UserVOFactory; import org.olat.util.logging.activity.LoggingResourceable; import com.thoughtworks.xstream.XStream; /** * Description:<br> * This web service will handle the functionality related to <code>Course</code> * and its contents. * * <P> * Initial Date: 27 apr. 2010 <br> * @author srosse, stephane.rosse@frentix.com */ public class CourseWebService { private static final OLog log = Tracing.createLoggerFor(CourseWebService.class); private static final XStream myXStream = XStreamHelper.createXStreamInstance(); private static final String VERSION = "1.0"; public static CacheControl cc = new CacheControl(); static { cc.setMaxAge(-1); } private final ICourse course; private final OLATResource courseOres; public CourseWebService(OLATResource courseOres, ICourse course) { this.course = course; this.courseOres = courseOres; } /** * The version of the Course Web Service * @response.representation.200.mediaType text/plain * @response.representation.200.doc The version of this specific Web Service * @response.representation.200.example 1.0 * @return */ @GET @Path("version") @Produces(MediaType.TEXT_PLAIN) public Response getVersion() { return Response.ok(VERSION).build(); } @Path("groups") public CourseGroupWebService getCourseGroupWebService() { RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(courseOres, false); return new CourseGroupWebService(re, courseOres); } @Path("calendar") public CalWebService getCourseCalendarWebService(@Context HttpServletRequest request) { CalendarModule calendarModule = CoreSpringFactory.getImpl(CalendarModule.class); if(calendarModule.isEnabled() && (calendarModule.isEnableCourseToolCalendar() || calendarModule.isEnableCourseElementCalendar()) && course.getCourseConfig().isCalendarEnabled()) { UserRequest ureq = getUserRequest(request); IdentityEnvironment ienv = new IdentityEnvironment(); ienv.setIdentity(ureq.getIdentity()); ienv.setRoles(ureq.getUserSession().getRoles()); UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, userCourseEnv, null); return new CalWebService(wrapper); } return null; } @Path("vitero/{subIdentifier}") public ViteroBookingWebService getViteroWebService(@PathParam("subIdentifier") String subIdentifier) { ViteroBookingWebService service = new ViteroBookingWebService(courseOres, subIdentifier); CoreSpringFactory.autowireObject(service); return service; } @Path("gotomeeting/{subIdentifier}") public GoToTrainingWebService getGoToMeetingWebService(@PathParam("subIdentifier") String subIdentifier) { RepositoryEntry courseRe = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); GoToTrainingWebService service = new GoToTrainingWebService(courseRe, subIdentifier); CoreSpringFactory.autowireObject(service); return service; } /** * Publish the course. * @response.representation.200.qname {http://www.example.com}courseVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The metadatas of the created course * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSEVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param locale The course locale * @param request The HTTP request * @return It returns the metadatas of the published course. */ @POST @Path("publish") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response publishCourse(@QueryParam("locale") Locale locale, @QueryParam("access") Integer access, @QueryParam("membersOnly") Boolean membersOnly, @Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } UserRequest ureq = getUserRequest(request); if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } int newAccess = access == null ? RepositoryEntry.ACC_USERS : access.intValue(); boolean members = membersOnly == null ? false : membersOnly.booleanValue(); CourseFactory.publishCourse(course, newAccess, members, ureq.getIdentity(), locale); CourseVO vo = ObjectFactory.get(course); return Response.ok(vo).build(); } /** * Get the metadatas of the course by id * @response.representation.200.qname {http://www.example.com}courseVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The metadatas of the created course * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSEVO} * @response.representation.404.doc The course not found * @return It returns the <code>CourseVO</code> object representing the course. */ @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response findById(@Context HttpServletRequest httpRequest) { if (!isCourseAccessible(course, false, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseVO vo = ObjectFactory.get(course); return Response.ok(vo).build(); } @GET @Path("resource") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getOlatResource(@Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } OlatResourceVO vo = new OlatResourceVO(course); return Response.ok(vo).build(); } /** * Export the course * @response.representation.200.mediaType application/zip * @response.representation.200.doc The course as a ZIP file * @response.representation.401.doc Not authorized to export the course * @response.representation.404.doc The course not found * @return It returns the <code>CourseVO</code> object representing the course. */ @GET @Path("file") @Produces({ "application/zip", MediaType.APPLICATION_OCTET_STREAM }) public Response getRepoFileById(@Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class); RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); if (re == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } RepositoryHandler typeToDownload = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); if (typeToDownload == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } OLATResource ores = OLATResourceManager.getInstance().findResourceable(re.getOlatResource()); if (ores == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } Identity identity = getIdentity(request); boolean isAuthor = RestSecurityHelper.isAuthor(request); boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(identity, re); if (!(isAuthor | isOwner)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } boolean canDownload = re.getCanDownload() && typeToDownload.supportsDownload(); if (!canDownload) { return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); } boolean isAlreadyLocked = typeToDownload.isLocked(ores); LockResult lockResult = null; try { lockResult = typeToDownload.acquireLock(ores, identity); if (lockResult == null || (lockResult != null && lockResult.isSuccess() && !isAlreadyLocked)) { MediaResource mr = typeToDownload.getAsMediaResource(ores, false); if (mr != null) { rs.incrementDownloadCounter(re); return Response.ok(mr.getInputStream()).cacheControl(cc).build(); // success } else { return Response.serverError().status(Status.NO_CONTENT).build(); } } else { return Response.serverError().status(Status.CONFLICT).build(); } } finally { if ((lockResult != null && lockResult.isSuccess() && !isAlreadyLocked)) { typeToDownload.releaseLock(lockResult); } } } /** * Delete a course by id. * * @response.representation.200.doc The metadatas of the deleted course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param request The HTTP request * @return It returns the XML representation of the <code>Structure</code> * object representing the course. */ @DELETE @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response deleteCourse(@Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } else if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } UserRequest ureq = getUserRequest(request); RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class); RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); ErrorList errors = rs.deletePermanently(re, ureq.getIdentity(), ureq.getUserSession().getRoles(), ureq.getLocale()); if(errors.hasErrors()) { return Response.serverError().status(500).build(); } ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_DELETE, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); return Response.ok().build(); } /** * Change the status of a course by id. The possible status are: * <ul> * <li>closed</li> * <li>unclosed</li> * <li>unpublished</li> * <li>deleted</li> * <li>restored</li> * </ul> * * @response.representation.200.doc The metadatas of the deleted course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param request The HTTP request * @return It returns the XML representation of the <code>Structure</code> * object representing the course. */ @POST @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Path("status") public Response deleteCoursePermanently(@FormParam("newStatus") String newStatus, @Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } else if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class); RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); if("closed".equals(newStatus)) { rs.closeRepositoryEntry(re); log.audit("REST closing course: " + re.getDisplayname() + " [" + re.getKey() + "]"); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_CLOSE, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); } else if("unclosed".equals(newStatus)) { rs.uncloseRepositoryEntry(re); log.audit("REST unclosing course: " + re.getDisplayname() + " [" + re.getKey() + "]"); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_UPDATE, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); } else if("unpublished".equals(newStatus)) { rs.unpublishRepositoryEntry(re); log.audit("REST unpublishing course: " + re.getDisplayname() + " [" + re.getKey() + "]"); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_DEACTIVATE, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); } else if("deleted".equals(newStatus)) { Identity identity = getIdentity(request); rs.deleteSoftly(re, identity, true); log.audit("REST deleting (soft) course: " + re.getDisplayname() + " [" + re.getKey() + "]"); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_TRASH, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); } else if("restored".equals(newStatus)) { rs.restoreRepositoryEntry(re); log.audit("REST restoring course: " + re.getDisplayname() + " [" + re.getKey() + "]"); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_RESTORE, getClass(), LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry)); } return Response.ok().build(); } /** * Get the configuration of the course * @response.representation.200.qname {http://www.example.com}courseConfigVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The configuration of the course * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSECONFIGVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param request The HTTP request * @return It returns the XML representation of the <code>Structure</code> * object representing the course. */ @GET @Path("configuration") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getConfiguration(@Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } else if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseConfigVO vo = ObjectFactory.getConfig(course); return Response.ok(vo).build(); } /** * Update the course configuration * @response.representation.200.qname {http://www.example.com}courseConfigVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The metadatas of the created course * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSECONFIGVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param courseId The course resourceable's id * @param calendar Enable/disable the calendar (value: true/false) (optional) * @param chat Enable/disable the chat (value: true/false) (optional) * @param cssLayoutRef Set the custom CSS file for the layout (optional) * @param efficencyStatement Enable/disable the efficencyStatement (value: true/false) (optional) * @param glossarySoftkey Set the glossary (optional) * @param sharedFolderSoftkey Set the shared folder (optional) * @param request The HTTP request * @return It returns the XML/Json representation of the <code>CourseConfig</code> * object representing the course configuration. */ @POST @Path("configuration") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response updateConfiguration(@PathParam("courseId") Long courseId, @FormParam("calendar") Boolean calendar, @FormParam("chat") Boolean chat, @FormParam("cssLayoutRef") String cssLayoutRef, @FormParam("efficencyStatement") Boolean efficencyStatement, @FormParam("glossarySoftkey") String glossarySoftkey, @FormParam("sharedFolderSoftkey") String sharedFolderSoftkey, @Context HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } ICourse editedCourse = CourseFactory.openCourseEditSession(courseId); //change course config CourseConfig courseConfig = editedCourse.getCourseEnvironment().getCourseConfig(); if(calendar != null) { courseConfig.setCalendarEnabled(calendar.booleanValue()); } if(chat != null) { courseConfig.setChatIsEnabled(chat.booleanValue()); } if(StringHelper.containsNonWhitespace(cssLayoutRef)) { courseConfig.setCssLayoutRef(cssLayoutRef); } if(efficencyStatement != null) { courseConfig.setEfficencyStatementIsEnabled(efficencyStatement.booleanValue()); } if(StringHelper.containsNonWhitespace(glossarySoftkey)) { courseConfig.setGlossarySoftKey(glossarySoftkey); } if(StringHelper.containsNonWhitespace(sharedFolderSoftkey)) { courseConfig.setSharedFolderSoftkey(sharedFolderSoftkey); } CourseFactory.setCourseConfig(editedCourse.getResourceableId(), courseConfig); CourseFactory.closeCourseEditSession(editedCourse.getResourceableId(),true); CourseConfigVO vo = ObjectFactory.getConfig(editedCourse); return Response.ok(vo).build(); } /** * Get the runstructure of the course by id * @response.representation.200.mediaType application/xml * @response.representation.200.doc The run structure of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param httpRequest The HTTP request * @param request The REST request * @return It returns the XML representation of the <code>Structure</code> * object representing the course. */ @GET @Path("runstructure") @Produces(MediaType.APPLICATION_XML) public Response findRunStructureById(@Context HttpServletRequest httpRequest, @Context Request request) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } VFSItem runStructureItem = course.getCourseBaseContainer().resolve("runstructure.xml"); Date lastModified = new Date(runStructureItem.getLastModified()); Response.ResponseBuilder response = request.evaluatePreconditions(lastModified); if(response == null) { return Response.ok(myXStream.toXML(course.getRunStructure())).build(); } return response.build(); } /** * Get the editor tree model of the course by id * @response.representation.200.mediaType application/xml * @response.representation.200.doc The editor tree model of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param httpRequest The HTTP request * @param request The REST request * @return It returns the XML representation of the <code>Editor model</code> * object representing the course. */ @GET @Path("editortreemodel") @Produces(MediaType.APPLICATION_XML) public Response findEditorTreeModelById(@Context HttpServletRequest httpRequest, @Context Request request) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } VFSItem editorModelItem = course.getCourseBaseContainer().resolve("editortreemodel.xml"); Date lastModified = new Date(editorModelItem.getLastModified()); Response.ResponseBuilder response = request.evaluatePreconditions(lastModified); if(response == null) { return Response.ok(myXStream.toXML(course.getEditorTreeModel())).build(); } return response.build(); } /** * Get all owners and authors of the course * @response.representation.200.qname {http://www.example.com}userVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The array of authors * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param httpRequest The HTTP request * @return It returns an array of <code>UserVO</code> */ @GET @Path("authors") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getAuthors(@Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); List<Identity> owners = repositoryService.getMembers(repositoryEntry, GroupRoles.owner.name()); int count = 0; UserVO[] authors = new UserVO[owners.size()]; for(Identity owner:owners) { authors[count++] = UserVOFactory.get(owner); } return Response.ok(authors).build(); } /** * Get all coaches of the course (don't follow the groups) * @response.representation.200.qname {http://www.example.com}userVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The array of coaches * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param httpRequest The HTTP request * @return It returns an array of <code>UserVO</code> */ @GET @Path("tutors") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getTutors(@Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); List<Identity> coachList = repositoryService.getMembers(repositoryEntry, GroupRoles.coach.name()); int count = 0; UserVO[] coaches = new UserVO[coachList.size()]; for(Identity coach:coachList) { coaches[count++] = UserVOFactory.get(coach); } return Response.ok(coaches).build(); } /** * Get all participants of the course (don't follow the groups) * @response.representation.200.qname {http://www.example.com}userVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The array of participants * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found * @param httpRequest The HTTP request * @return It returns an array of <code>UserVO</code> */ @GET @Path("participants") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getParticipants(@Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); List<Identity> participantList = repositoryService.getMembers(repositoryEntry, GroupRoles.participant.name()); int count = 0; UserVO[] participants = new UserVO[participantList.size()]; for(Identity participant:participantList) { participants[count++] = UserVOFactory.get(participant); } return Response.ok(participants).build(); } /** * Get this specific author and owner of the course * @response.representation.200.qname {http://www.example.com}userVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The author * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course not found or the user is not an onwer or author of the course * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns an <code>UserVO</code> */ @GET @Path("authors/{identityKey}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getAuthor(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); BaseSecurity securityManager = BaseSecurityManager.getInstance(); SecurityGroup authorGroup = securityManager.findSecurityGroupByName(Constants.GROUP_AUTHORS); Identity author = securityManager.loadIdentityByKey(identityKey, false); if(repositoryService.hasRole(author, repositoryEntry, GroupRoles.owner.name()) && securityManager.isIdentityInSecurityGroup(author, authorGroup)) { UserVO vo = UserVOFactory.get(author); return Response.ok(vo).build(); } return Response.ok(author).build(); } /** * Add an owner and author to the course * @response.representation.200.doc The user is an author and owner of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or the user not found * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns 200 if the user is added as owner and author of the course */ @PUT @Path("authors/{identityKey}") public Response addAuthor(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } BaseSecurity securityManager = BaseSecurityManager.getInstance(); Identity author = securityManager.loadIdentityByKey(identityKey, false); if(author == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } Identity identity = getIdentity(httpRequest); SecurityGroup authorGroup = securityManager.findSecurityGroupByName(Constants.GROUP_AUTHORS); boolean hasBeenAuthor = securityManager.isIdentityInSecurityGroup(author, authorGroup); if(!hasBeenAuthor) { //not an author already, add this identity to the security group "authors" securityManager.addIdentityToSecurityGroup(author, authorGroup); log.audit("User::" + identity.getName() + " added system role::" + Constants.GROUP_AUTHORS + " to user::" + author.getName() + " via addAuthor method in course REST API", null); } //add the author as owner of the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); List<Identity> authors = Collections.singletonList(author); IdentitiesAddEvent identitiesAddedEvent = new IdentitiesAddEvent(authors); rm.addOwners(identity, identitiesAddedEvent, repositoryEntry); return Response.ok().build(); } @PUT @Path("authors") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response addAuthors(UserVO[] authors, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } BaseSecurity securityManager = BaseSecurityManager.getInstance(); List<Identity> authorList = loadIdentities(authors); Identity identity = getIdentity(httpRequest); SecurityGroup authorGroup = securityManager.findSecurityGroupByName(Constants.GROUP_AUTHORS); for(Identity author:authorList) { boolean hasBeenAuthor = securityManager.isIdentityInSecurityGroup(author, authorGroup); if(!hasBeenAuthor) { //not an author already, add this identity to the security group "authors" securityManager.addIdentityToSecurityGroup(author, authorGroup); log.audit("User::" + identity.getName() + " added system role::" + Constants.GROUP_AUTHORS + " to user::" + author.getName() + " via addAuthor method in course REST API", null); } } //add the author as owner of the course RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); IdentitiesAddEvent identitiesAddedEvent = new IdentitiesAddEvent(authorList); RepositoryManager.getInstance().addOwners(identity, identitiesAddedEvent, repositoryEntry); return Response.ok().build(); } /** * Remove an owner and author to the course * @response.representation.200.doc The user was successfully removed as owner of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or the user not found * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns 200 if the user is removed as owner of the course */ @DELETE @Path("authors/{identityKey}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response removeAuthor(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } BaseSecurity securityManager = BaseSecurityManager.getInstance(); Identity author = securityManager.loadIdentityByKey(identityKey, false); if(author == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } Identity identity = getIdentity(httpRequest); //remove the author as owner of the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); List<Identity> authors = Collections.singletonList(author); rm.removeOwners(identity, authors, repositoryEntry); return Response.ok().build(); } /** * Add a coach to the course * @response.representation.200.doc The user is a coach of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or the user not found * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns 200 if the user is added as coach of the course */ @PUT @Path("tutors/{identityKey}") public Response addCoach(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } BaseSecurity securityManager = BaseSecurityManager.getInstance(); Identity tutor = securityManager.loadIdentityByKey(identityKey, false); if(tutor == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } Identity identity = getIdentity(httpRequest); UserRequest ureq = getUserRequest(httpRequest); //add the author as owner of the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); List<Identity> tutors = Collections.singletonList(tutor); IdentitiesAddEvent iae = new IdentitiesAddEvent(tutors); rm.addTutors(identity, ureq.getUserSession().getRoles(), iae, repositoryEntry, new MailPackage(false)); return Response.ok().build(); } @PUT @Path("tutors") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response addCoaches(UserVO[] coaches, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } List<Identity> coachList = loadIdentities(coaches); Identity identity = getIdentity(httpRequest); UserRequest ureq = getUserRequest(httpRequest); //add the author as owner of the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); IdentitiesAddEvent iae = new IdentitiesAddEvent(coachList); rm.addTutors(identity, ureq.getUserSession().getRoles(), iae, repositoryEntry, new MailPackage(false)); return Response.ok().build(); } /** * Add an participant to the course * @response.representation.200.doc The user is a participant of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or the user not found * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns 200 if the user is added as owner and author of the course */ @PUT @Path("participants/{identityKey}") public Response addParticipant(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } BaseSecurity securityManager = BaseSecurityManager.getInstance(); Identity participant = securityManager.loadIdentityByKey(identityKey, false); if(participant == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } Identity identity = getIdentity(httpRequest); UserRequest ureq = getUserRequest(httpRequest); //add the author as owner of the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); List<Identity> participants = Collections.singletonList(participant); IdentitiesAddEvent iae = new IdentitiesAddEvent(participants); rm.addParticipants(identity, ureq.getUserSession().getRoles(), iae, repositoryEntry, new MailPackage(false)); return Response.ok().build(); } /** * Add an participant to the course * @response.representation.200.doc The user is a participant of the course * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or the user not found * @param identityKey The user identifier * @param httpRequest The HTTP request * @return It returns 200 if the user is added as owner and author of the course */ @PUT @Path("participants") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response addParticipants(UserVO[] participants, @Context HttpServletRequest httpRequest) { if (!isAuthorEditor(course, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } List<Identity> participantList = loadIdentities(participants); Identity identity = getIdentity(httpRequest); UserRequest ureq = getUserRequest(httpRequest); //add the participants to the course RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); IdentitiesAddEvent iae = new IdentitiesAddEvent(participantList); rm.addParticipants(identity, ureq.getUserSession().getRoles(), iae, repositoryEntry, new MailPackage(false)); return Response.ok().build(); } private List<Identity> loadIdentities(UserVO[] users) { List<Long> identityKeys = new ArrayList<>(); for(UserVO user:users) { identityKeys.add(user.getKey()); } return BaseSecurityManager.getInstance().loadIdentityByKeys(identityKeys); } public static boolean isCourseAccessible(ICourse course, boolean authorRightsMandatory, HttpServletRequest request) { if(isAdmin(request)) { return true; } if(authorRightsMandatory && !isAuthor(request)) { return false; } Identity identity = getIdentity(request); RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); ACService acManager = CoreSpringFactory.getImpl(ACService.class); AccessResult result = acManager.isAccessible(entry, identity, false); if(result.isAccessible()) { return true; } return false; } }