/** * <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.resource.accesscontrol.manager; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.SortKey; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; import org.olat.group.manager.BusinessGroupDAO; import org.olat.group.manager.BusinessGroupRelationDAO; import org.olat.group.model.EnrollState; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.RepositoryEntryShort; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.manager.RepositoryEntryRelationDAO; import org.olat.repository.model.RepositoryEntryShortImpl; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.resource.accesscontrol.ACService; import org.olat.resource.accesscontrol.AccessControlModule; import org.olat.resource.accesscontrol.AccessResult; import org.olat.resource.accesscontrol.AccessTransaction; import org.olat.resource.accesscontrol.Offer; import org.olat.resource.accesscontrol.OfferAccess; import org.olat.resource.accesscontrol.Order; import org.olat.resource.accesscontrol.OrderStatus; import org.olat.resource.accesscontrol.ResourceReservation; import org.olat.resource.accesscontrol.method.AccessMethodHandler; import org.olat.resource.accesscontrol.model.ACResourceInfo; import org.olat.resource.accesscontrol.model.ACResourceInfoImpl; import org.olat.resource.accesscontrol.model.AccessMethod; import org.olat.resource.accesscontrol.model.AccessTransactionStatus; import org.olat.resource.accesscontrol.model.OLATResourceAccess; import org.olat.resource.accesscontrol.model.PSPTransactionStatus; import org.olat.resource.accesscontrol.model.RawOrderItem; import org.olat.resource.accesscontrol.ui.OrderTableItem; import org.olat.resource.accesscontrol.ui.OrderTableItem.Status; import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Description:<br> * The access control is not intend for security check. * * <P> * Initial Date: 14 avr. 2011 <br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ @Service("acService") public class ACFrontendManager implements ACService { private static final OLog log = Tracing.createLoggerFor(ACFrontendManager.class); @Autowired private DB dbInstance; @Autowired private RepositoryManager repositoryManager; @Autowired private RepositoryService repositoryService; @Autowired private AccessControlModule accessModule; @Autowired private ACOfferDAO accessManager; @Autowired private ACMethodDAO methodManager; @Autowired private ACOrderDAO orderManager; @Autowired private ACReservationDAO reservationDao; @Autowired private ACTransactionDAO transactionManager; @Autowired private BusinessGroupDAO businessGroupDao; @Autowired private BusinessGroupRelationDAO businessGroupRelationDao; @Autowired private RepositoryEntryRelationDAO repositoryEntryRelationDao; @Autowired private BusinessGroupService businessGroupService; /** * The rule to access the repository entry:<br/> * -No offer, access is free<br/> * -Owners have always access to the resource<br/> * -Tutors have access to the resource<br/> * -Participants have access to the resource<br/> * @param entry * @param forId * @param knowMember give it if already know as a member * @return */ @Override public AccessResult isAccessible(RepositoryEntry entry, Identity forId, Boolean knowMember, boolean allowNonInteractiveAccess) { if(!accessModule.isEnabled()) { return new AccessResult(true); } boolean member; if(knowMember == null) { member = repositoryService.isMember(forId, entry); } else { member = knowMember.booleanValue(); } if(member) { return new AccessResult(true); } List<Offer> offers = accessManager.findOfferByResource(entry.getOlatResource(), true, new Date()); if(offers.isEmpty()) { if(methodManager.isValidMethodAvailable(entry.getOlatResource(), null)) { //not open for the moment: no valid offer at this date but some methods are defined return new AccessResult(false); } else { return new AccessResult(true); } } return isAccessible(forId, offers, allowNonInteractiveAccess); } @Override public AccessResult isAccessible(RepositoryEntry entry, Identity forId, boolean allowNonInteractiveAccess) { if(!accessModule.isEnabled()) { return new AccessResult(true); } boolean member = repositoryService.isMember(forId, entry); return isAccessible(entry, forId, new Boolean(member), allowNonInteractiveAccess); } /** * * @param resource * @param atDate * @return */ @Override public boolean isResourceAccessControled(OLATResource resource, Date atDate) { return methodManager.isValidMethodAvailable(resource, atDate); } /** * The rule to access a business group:<br/> * -No offer, access is free<br/> * -Owners have always access to the resource<br/> * -Tutors have access to the resource<br/> * -Participants have access to the resource<br/> * @param group * @param forId * @return */ @Override public AccessResult isAccessible(BusinessGroup group, Identity forId, boolean allowNonInteractiveAccess) { if(!accessModule.isEnabled()) { return new AccessResult(true); } List<String> roles = businessGroupRelationDao.getRoles(forId, group); if(roles.contains(GroupRoles.coach.name())) { return new AccessResult(true); } if(roles.contains(GroupRoles.participant.name())) { return new AccessResult(true); } OLATResource resource = OLATResourceManager.getInstance().findResourceable(group); List<Offer> offers = accessManager.findOfferByResource(resource, true, new Date()); if(offers.isEmpty()) { if(methodManager.isValidMethodAvailable(resource, null)) { //not open for the moment: no valid offer at this date but some methods are defined return new AccessResult(false); } else { return new AccessResult(true); } } return isAccessible(forId, offers, allowNonInteractiveAccess); } protected AccessResult isAccessible(Identity identity, List<Offer> offers, boolean allowNonInteractiveAccess) { List<OfferAccess> offerAccess = methodManager.getOfferAccess(offers, true); if(offerAccess.isEmpty()) { return new AccessResult(false); } if(allowNonInteractiveAccess && offerAccess.size() == 1) { //is it a method without user interaction as the free access? OfferAccess link = offerAccess.get(0); if(!link.getMethod().isNeedUserInteraction()) { return accessResource(identity, link, null); } } return new AccessResult(false, offerAccess); } @Override public Offer createOffer(OLATResource resource, String resourceName) { return accessManager.createOffer(resource, resourceName); } @Override public void deleteOffer(Offer offer) { accessManager.deleteOffer(offer); } @Override public List<OLATResourceAccess> filterRepositoryEntriesWithAC(List<RepositoryEntry> repoEntries) { if(repoEntries == null || repoEntries.isEmpty()) { return Collections.emptyList(); } Set<String> resourceTypes = new HashSet<String>(); List<Long> resourceKeys = new ArrayList<Long>(); for(RepositoryEntry entry:repoEntries) { OLATResource ores = entry.getOlatResource(); resourceKeys.add(ores.getKey()); resourceTypes.add(ores.getResourceableTypeName()); } String resourceType = null; if(resourceTypes.size() == 1) { resourceType = resourceTypes.iterator().next(); } return methodManager.getAccessMethodForResources(resourceKeys, resourceType, "BusinessGroup", true, new Date()); } @Override public List<OLATResourceAccess> filterResourceWithAC(List<OLATResource> resources) { if(resources == null || resources.isEmpty()) { return Collections.emptyList(); } Set<String> resourceTypes = new HashSet<String>(); List<Long> resourceKeys = new ArrayList<Long>(); for(OLATResource resource:resources) { resourceKeys.add(resource.getKey()); resourceTypes.add(resource.getResourceableTypeName()); } String resourceType = null; if(resourceTypes.size() == 1) { resourceType = resourceTypes.iterator().next(); } return methodManager.getAccessMethodForResources(resourceKeys, resourceType, "BusinessGroup", true, new Date()); } @Override public Set<Long> filterResourcesWithAC(Collection<Long> resourceKeys) { return accessManager.filterResourceWithOffer(resourceKeys); } @Override public List<Offer> findOfferByResource(OLATResource resource, boolean valid, Date atDate) { return accessManager.findOfferByResource(resource, valid, atDate); } /** * * @param resourceKeys This parameter is mandatory and must not be empty! */ @Override public List<OLATResourceAccess> getAccessMethodForResources(Collection<Long> resourceKeys, String resourceType, boolean valid, Date atDate) { if(resourceKeys == null || resourceKeys.isEmpty()) { return new ArrayList<OLATResourceAccess>(); } return methodManager.getAccessMethodForResources(resourceKeys, resourceType, null, valid, atDate); } /** * Get the list of access methods for a business group that are currently available * @param group * @param valid * @param atDate * @return The list of OfferAccess objects that represent available access methods */ @Override public List<OfferAccess> getAccessMethodForBusinessGroup(BusinessGroup group, boolean valid, Date atDate) { List<Offer> offers = accessManager.findOfferByResource(group.getResource(), valid, atDate); if(offers.isEmpty()) { return Collections.<OfferAccess>emptyList(); } List<OfferAccess> offerAccess = methodManager.getOfferAccess(offers, valid); if(offerAccess.isEmpty()) { return Collections.<OfferAccess>emptyList(); } return offerAccess; } @Override public Offer save(Offer offer) { return accessManager.saveOffer(offer); } @Override public OfferAccess saveOfferAccess(OfferAccess link) { //offer access only cascade merge if(link.getOffer().getKey() == null) { accessManager.saveOffer(link.getOffer()); } return methodManager.save(link); } @Override public AccessResult accessResource(Identity identity, OfferAccess link, Object argument) { if(link == null || link.getOffer() == null || link.getMethod() == null) { log.audit("Access refused (no offer) to: " + link + " for " + identity); return new AccessResult(false); } AccessMethodHandler handler = accessModule.getAccessMethodHandler(link.getMethod().getType()); if(handler == null) { log.audit("Access refused (no handler method) to: " + link + " for " + identity); return new AccessResult(false); } if(handler.checkArgument(link, argument)) { if(allowAccesToResource(identity, link.getOffer())) { Order order = orderManager.saveOneClick(identity, link); AccessTransaction transaction = transactionManager.createTransaction(order, order.getParts().get(0), link.getMethod()); transactionManager.save(transaction); dbInstance.commit(); log.audit("Access granted to: " + link + " for " + identity); return new AccessResult(true); } else { log.audit("Access error to: " + link + " for " + identity); } } else { log.audit("Access refused to: " + link + " for " + identity); } return new AccessResult(false); } @Override public void acceptReservationToResource(Identity identity, ResourceReservation reservation) { OLATResource resource = reservation.getResource(); if("BusinessGroup".equals(resource.getResourceableTypeName())) { //it's a reservation for a group businessGroupService.acceptPendingParticipation(identity, identity, resource); } else { repositoryManager.acceptPendingParticipation(identity, identity, resource, reservation); } } @Override public void removeReservation(Identity ureqIdentity, Identity identity, ResourceReservation reservation) { OLATResource resource = reservation.getResource(); reservationDao.deleteReservation(reservation); if("BusinessGroup".equals(resource.getResourceableTypeName())) { dbInstance.commit();//needed to have the right number of participants to calculate upgrade from waiting list businessGroupService.cancelPendingParticipation(ureqIdentity, reservation); } } @Override public ResourceReservation getReservation(Identity identity, OLATResource resource) { return reservationDao.loadReservation(identity, resource); } @Override public List<ResourceReservation> getReservations(List<OLATResource> resources) { return reservationDao.loadReservations(resources); } @Override public List<ResourceReservation> getReservations(Identity identity) { return reservationDao.loadReservations(identity); } @Override public int countReservations(OLATResource resource) { return reservationDao.countReservations(resource); } @Override public boolean reserveAccessToResource(final Identity identity, final OfferAccess offer) { final OLATResource resource = offer.getOffer().getResource(); String resourceType = resource.getResourceableTypeName(); if("BusinessGroup".equals(resourceType)) { boolean reserved = false; final BusinessGroup group = businessGroupDao.loadForUpdate(resource.getResourceableId()); if(group.getMaxParticipants() == null && group.getMaxParticipants() <= 0) { reserved = true;//don't need reservation } else { BusinessGroup reloadedGroup = businessGroupService.loadBusinessGroup(resource); ResourceReservation reservation = reservationDao.loadReservation(identity, resource); if(reservation != null) { reserved = true; } int currentCount = businessGroupService.countMembers(reloadedGroup, GroupRoles.participant.name()); int reservations = reservationDao.countReservations(resource); if(currentCount + reservations < reloadedGroup.getMaxParticipants().intValue()) { reservationDao.createReservation(identity, offer.getMethod().getType(), null, resource); reserved = true; } } return reserved; } return true; } @Override public void cleanupReservations() { Calendar cal = Calendar.getInstance(); cal.add(Calendar.HOUR_OF_DAY, -1); Date oneHourTimeout = cal.getTime(); List<ResourceReservation> oldReservations = reservationDao.loadExpiredReservation(oneHourTimeout); for(ResourceReservation reservation:oldReservations) { log.audit("Remove reservation:" + reservation); reservationDao.deleteReservation(reservation); } } @Override public boolean allowAccesToResource(final Identity identity, final Offer offer) { //check if offer is ok: key is stupid but further check as date, validity... if(offer.getKey() == null) { return false; } //check the resource OLATResource resource = offer.getResource(); if(resource == null || resource.getKey() == null || resource.getResourceableId() == null || resource.getResourceableTypeName() == null) { return false; } String resourceType = resource.getResourceableTypeName(); if("BusinessGroup".equals(resourceType)) { BusinessGroup group = businessGroupService.loadBusinessGroup(resource); if(group != null) { EnrollState result = businessGroupService.enroll(identity, null, identity, group, null); return result.isFailed() ? Boolean.FALSE : Boolean.TRUE; } } else { RepositoryEntryRef entry = repositoryManager.lookupRepositoryEntry(resource, false); if(entry != null) { if(!repositoryEntryRelationDao.hasRole(identity, entry, GroupRoles.participant.name())) { repositoryEntryRelationDao.addRole(identity, entry, GroupRoles.participant.name()); } return true; } } return false; } @Override public boolean denyAccesToResource(Identity identity, Offer offer) { //check if offer is ok: key is stupid but further check as date, validity... if(offer.getKey() == null) { return false; } //check the resource OLATResource resource = offer.getResource(); if(resource == null || resource.getKey() == null || resource.getResourceableId() == null || resource.getResourceableTypeName() == null) { return false; } String resourceType = resource.getResourceableTypeName(); if("BusinessGroup".equals(resourceType)) { BusinessGroup group = businessGroupService.loadBusinessGroup(resource); if(group != null) { if(businessGroupService.hasRoles(identity, group, GroupRoles.participant.name())) { businessGroupRelationDao.removeRole(identity, group, GroupRoles.participant.name()); } return true; } } else { RepositoryEntryRef entry = repositoryManager.lookupRepositoryEntry(resource, false); if(entry != null) { if(repositoryEntryRelationDao.hasRole(identity, entry, GroupRoles.participant.name())) { repositoryEntryRelationDao.removeRole(identity, entry, GroupRoles.participant.name()); } return true; } } return false; } @Override public String resolveDisplayName(OLATResource resource) { String resourceType = resource.getResourceableTypeName(); if("BusinessGroup".equals(resourceType)) { BusinessGroup group = businessGroupService.loadBusinessGroup(resource); if(group != null) { return group.getName(); } } else { RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(resource, false); if(entry != null) { return entry.getDisplayname(); } } return null; } @Override public List<ACResourceInfo> getResourceInfos(List<OLATResource> resources) { if(resources == null || resources.isEmpty()) { return Collections.emptyList(); } List<OLATResource> groupResources = new ArrayList<OLATResource>(resources.size()); List<OLATResource> repositoryResources = new ArrayList<OLATResource>(resources.size()); for(OLATResource resource:resources) { String resourceType = resource.getResourceableTypeName(); if("BusinessGroup".equals(resourceType)) { groupResources.add(resource); } else { repositoryResources.add(resource); } } List<ACResourceInfo> resourceInfos = new ArrayList<ACResourceInfo>(resources.size()); if(!groupResources.isEmpty()) { List<Long> groupKeys = new ArrayList<Long>(groupResources.size()); Map<Long, OLATResource> groupMapKeys = new HashMap<Long, OLATResource>(groupResources.size() * 2 + 1); for(OLATResource groupResource:groupResources) { groupKeys.add(groupResource.getResourceableId()); } List<BusinessGroup> groups = businessGroupService.loadBusinessGroups(groupKeys); for(BusinessGroup group:groups) { ACResourceInfoImpl info = new ACResourceInfoImpl(); info.setResource(groupMapKeys.get(group.getKey())); info.setName(group.getName()); info.setDescription(group.getDescription()); info.setResource(group.getResource()); resourceInfos.add(info); } } if(!repositoryResources.isEmpty()) { List<RepositoryEntryShort> repoEntries = repositoryManager.loadRepositoryEntryShorts(repositoryResources); for(RepositoryEntryShort repoEntry:repoEntries) { ACResourceInfoImpl info = new ACResourceInfoImpl(); info.setName(repoEntry.getDisplayname()); info.setDescription(((RepositoryEntryShortImpl)repoEntry).getDescription()); info.setResource(((RepositoryEntryShortImpl)repoEntry).getOlatResource()); resourceInfos.add(info); } } return resourceInfos; } @Override public void enableMethod(Class<? extends AccessMethod> type, boolean enable) { methodManager.enableMethod(type, enable); } @Override public List<AccessMethod> getAvailableMethods(Identity identity, Roles roles) { return methodManager.getAvailableMethods(identity, roles); } @Override public OfferAccess createOfferAccess(Offer offer, AccessMethod method) { return methodManager.createOfferAccess(offer, method); } @Override public void deletedLinkToMethod(OfferAccess link) { methodManager.delete(link); } @Override public List<OfferAccess> getOfferAccess(Offer offer, boolean valid) { return methodManager.getOfferAccess(offer, valid); } @Override public List<Order> findOrders(Identity delivery, OrderStatus... status) { return orderManager.findOrdersByDelivery(delivery, status); } @Override public List<AccessTransaction> findAccessTransactions(Order order) { return transactionManager.loadTransactionsForOrder(order); } @Override public Order loadOrderByKey(Long key) { return orderManager.loadOrderByKey(key); } @Override public List<Order> findOrders(OLATResource resource, OrderStatus... status) { return orderManager.findOrdersByResource(resource, status); } @Override public int countOrderItems(OLATResource resource, IdentityRef delivery, Long orderNr, Date from, Date to, OrderStatus[] status) { return orderManager.countNativeOrderItems(resource, delivery, orderNr, from, to, status); } @Override public List<OrderTableItem> findOrderItems(OLATResource resource, IdentityRef delivery, Long orderNr, Date from, Date to, OrderStatus[] status, int firstResult, int maxResults, List<UserPropertyHandler> userPropertyHandlers, SortKey... orderBy) { List<AccessMethod> methods = methodManager.getAllMethods(); Map<String,AccessMethod> methodMap = new HashMap<>(); for(AccessMethod method:methods) { methodMap.put(method.getKey().toString(), method); } List<RawOrderItem> rawOrders = orderManager.findNativeOrderItems(resource, delivery, orderNr, from, to, status, firstResult, maxResults, userPropertyHandlers, orderBy); List<OrderTableItem> items = new ArrayList<>(rawOrders.size()); for(RawOrderItem rawOrder:rawOrders) { String orderStatusStr = rawOrder.getOrderStatus(); OrderStatus orderStatus = OrderStatus.valueOf(orderStatusStr); Status finalStatus = getStatus(orderStatusStr, rawOrder.getTrxStatus(), rawOrder.getPspTrxStatus()); String methodIds = rawOrder.getTrxMethodIds(); List<AccessMethod> orderMethods = new ArrayList<>(2); if(StringHelper.containsNonWhitespace(methodIds)) { String[] methodIdArr = methodIds.split(","); for(String methodId:methodIdArr) { if(methodMap.containsKey(methodId)) { orderMethods.add(methodMap.get(methodId)); } } } OrderTableItem item = new OrderTableItem(rawOrder.getOrderKey(), rawOrder.getOrderNr(), rawOrder.getTotal(), rawOrder.getCreationDate(), orderStatus, finalStatus, rawOrder.getDeliveryKey(), rawOrder.getUsername(), rawOrder.getUserProperties(), orderMethods); item.setResourceDisplayname(rawOrder.getResourceName()); items.add(item); } return items; } public Status getStatus(String orderStatus, String trxStatus, String pspTrxStatus) { boolean warning = false; boolean error = false; boolean canceled = false; if(OrderStatus.CANCELED.name().equals(orderStatus)) { canceled = true; } else if(OrderStatus.ERROR.name().equals(orderStatus)) { error = true; } else if(OrderStatus.PREPAYMENT.name().equals(orderStatus)) { warning = true; } if(StringHelper.containsNonWhitespace(trxStatus)) { if(trxStatus.contains(AccessTransactionStatus.CANCELED.name())) { canceled = true; } else if(trxStatus.contains(AccessTransactionStatus.ERROR.name())) { error = true; } } if(StringHelper.containsNonWhitespace(pspTrxStatus)) { if(pspTrxStatus.contains(PSPTransactionStatus.ERROR.name())) { error = true; } else if(pspTrxStatus.contains(PSPTransactionStatus.WARNING.name())) { warning = true; } } if(error) { return Status.ERROR; } else if (warning) { return Status.WARNING; } else if(canceled) { return Status.CANCELED; } else { return Status.OK; } } }