/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.groups.pags.dao;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IGroupConstants;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IPermission;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.RuntimeAuthorizationException;
import org.apereo.portal.services.AuthorizationService;
import org.apereo.portal.services.GroupService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Service layer that sits atop the DAO layer and enforces permissions and/or business rules that
* apply to CRUD operations on PAGS definitions. External clients should interact with this service
* -- instead of the DAOs directly -- whenever the actions are undertaken on behalf of a specific
* user.
*
*/
@Service
public final class PagsService {
// These 2 should be (public) constants on their respective service classes, not here
private static final String SERVICE_NAME_LOCAL = "local";
public static final String SERVICE_NAME_PAGS = "pags";
private static final String GROUP_NAME_VALIDATOR_REGEX = "^[\\w ]{5,500}$"; // 5-500 characters
private static final Pattern GROUP_NAME_VALIDATOR_PATTERN =
Pattern.compile(GROUP_NAME_VALIDATOR_REGEX);
private static final String GROUP_DESC_VALIDATOR_REGEX =
"^[\\w ,\\.\\(\\)]{0,500}$"; // 0-500 characters
private static final Pattern GROUP_DESC_VALIDATOR_PATTERN =
Pattern.compile(GROUP_DESC_VALIDATOR_REGEX);
@Autowired private IPersonAttributesGroupDefinitionDao pagsGroupDefDao;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* All the definitions, filtered by the user's access rights.
*
* @param person
* @return
*/
public Set<IPersonAttributesGroupDefinition> getPagsDefinitions(IPerson person) {
Set<IPersonAttributesGroupDefinition> rslt = new HashSet<>();
for (IPersonAttributesGroupDefinition def :
pagsGroupDefDao.getPersonAttributesGroupDefinitions()) {
if (hasPermission(
person,
IPermission.VIEW_GROUP_ACTIVITY,
def.getCompositeEntityIdentifierForGroup().getKey())) {
rslt.add(def);
}
}
logger.debug("Returning PAGS definitions '{}' for user '{}'", rslt, person.getUserName());
return rslt;
}
/**
* Returns the specified definitions, provided (1) it exists and (2) the user may view it.
*
* @param person
* @return
*/
public IPersonAttributesGroupDefinition getPagsDefinitionByName(IPerson person, String name) {
IPersonAttributesGroupDefinition rslt = getPagsGroupDefByName(name);
if (rslt == null) {
// Better to produce exception? I'm thinking not, but open-minded.
return null;
}
if (!hasPermission(
person,
IPermission.VIEW_GROUP_ACTIVITY,
rslt.getCompositeEntityIdentifierForGroup().getKey())) {
throw new RuntimeAuthorizationException(person, IPermission.VIEW_GROUP_ACTIVITY, name);
}
logger.debug("Returning PAGS definition '{}' for user '{}'", rslt, person.getUserName());
return rslt;
}
/** Verifies permissions and that the group doesn't already exist */
public IPersonAttributesGroupDefinition createPagsDefinition(
IPerson person, IEntityGroup parent, String groupName, String description) {
// What's the target of the upcoming permissions check?
String target =
parent != null
? parent.getEntityIdentifier().getKey()
: IPermission
.ALL_GROUPS_TARGET; // Must have blanket permission to create one w/o a parent
// Verify permission
if (!hasPermission(person, IPermission.CREATE_GROUP_ACTIVITY, target)) {
throw new RuntimeAuthorizationException(
person, IPermission.CREATE_GROUP_ACTIVITY, target);
}
// VALIDATION STEP: The group name & description are allowable
if (StringUtils.isBlank(groupName)) {
throw new IllegalArgumentException("Specified groupName is blank: " + groupName);
}
if (!GROUP_NAME_VALIDATOR_PATTERN.matcher(groupName).matches()) {
throw new IllegalArgumentException(
"Specified groupName is too long, too short, or contains invalid characters: "
+ groupName);
}
if (!StringUtils.isBlank(description)) { // Blank description is allowable
if (!GROUP_DESC_VALIDATOR_PATTERN.matcher(description).matches()) {
throw new IllegalArgumentException(
"Specified description is too long or contains invalid characters: "
+ description);
}
}
// VALIDATION STEP: We don't have a group by that name already
EntityIdentifier[] people =
GroupService.searchForGroups(groupName, IGroupConstants.IS, IPerson.class);
EntityIdentifier[] portlets =
GroupService.searchForGroups(
groupName, IGroupConstants.IS, IPortletDefinition.class);
if (people.length != 0 || portlets.length != 0) {
throw new IllegalArgumentException("Specified groupName already in use: " + groupName);
}
IPersonAttributesGroupDefinition rslt =
pagsGroupDefDao.createPersonAttributesGroupDefinition(groupName, description);
if (parent != null) {
// Should refactor this switch to instead choose a service and invoke a method on it
switch (parent.getServiceName().toString()) {
case SERVICE_NAME_LOCAL:
IEntityGroup member =
GroupService.findGroup(
rslt.getCompositeEntityIdentifierForGroup().getKey());
if (member == null) {
String msg =
"The specified group was created, but is not present in the store: "
+ rslt.getName();
throw new RuntimeException(msg);
}
parent.addChild(member);
parent.updateMembers();
break;
case SERVICE_NAME_PAGS:
IPersonAttributesGroupDefinition parentDef =
getPagsGroupDefByName(parent.getName());
Set<IPersonAttributesGroupDefinition> members =
new HashSet<>(parentDef.getMembers());
members.add(rslt);
parentDef.setMembers(members);
pagsGroupDefDao.updatePersonAttributesGroupDefinition(parentDef);
break;
default:
String msg =
"The specified group service does not support adding members: "
+ parent.getServiceName();
throw new UnsupportedOperationException(msg);
}
}
return rslt;
}
/** NOTE -- This method assumes that pagsDef is an existing JPA-managed entity. */
public IPersonAttributesGroupDefinition updatePagsDefinition(
IPerson person, IPersonAttributesGroupDefinition pagsDef) {
// Verify permission
if (!hasPermission(
person,
IPermission.EDIT_GROUP_ACTIVITY,
pagsDef.getCompositeEntityIdentifierForGroup().getKey())) {
throw new RuntimeAuthorizationException(
person,
IPermission.EDIT_GROUP_ACTIVITY,
pagsDef.getCompositeEntityIdentifierForGroup().getKey());
}
IPersonAttributesGroupDefinition rslt =
pagsGroupDefDao.updatePersonAttributesGroupDefinition(pagsDef);
return rslt;
}
/** NOTE -- This method assumes that pagsDef is an existing JPA-managed entity. */
public void deletePagsDefinition(IPerson person, IPersonAttributesGroupDefinition pagsDef) {
// Verify permission
if (!hasPermission(
person,
IPermission.DELETE_GROUP_ACTIVITY,
pagsDef.getCompositeEntityIdentifierForGroup().getKey())) {
throw new RuntimeAuthorizationException(
person,
IPermission.DELETE_GROUP_ACTIVITY,
pagsDef.getCompositeEntityIdentifierForGroup().getKey());
}
pagsGroupDefDao.deletePersonAttributesGroupDefinition(pagsDef);
}
/*
* Implementation
*/
private boolean hasPermission(IPerson person, String permission, String target) {
EntityIdentifier ei = person.getEntityIdentifier();
IAuthorizationPrincipal ap =
AuthorizationService.instance().newPrincipal(ei.getKey(), ei.getType());
return ap.hasPermission(IPermission.PORTAL_GROUPS, permission, target);
}
private IPersonAttributesGroupDefinition getPagsGroupDefByName(String name) {
Set<IPersonAttributesGroupDefinition> pagsGroups =
pagsGroupDefDao.getPersonAttributesGroupDefinitionByName(name);
if (pagsGroups.size() > 1) {
logger.error("More than one PAGS group with name {} found.", name);
}
return pagsGroups.isEmpty() ? null : pagsGroups.iterator().next();
}
}