/*
* Copyright (C) 2005-2008 BetaCONCEPT LP.
*
* This file is part of Astroboa.
*
* Astroboa is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Astroboa is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Astroboa. If not, see <http://www.gnu.org/licenses/>.
*/
package org.betaconceptframework.astroboa.context;
import java.io.Serializable;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.security.AstroboaPrincipalName;
import org.betaconceptframework.astroboa.api.security.IdentityPrincipal;
import org.betaconceptframework.astroboa.security.CmsGroup;
import org.betaconceptframework.astroboa.security.CmsPrincipal;
/**
* This class contains all necessary security information about
* an authenticated user or entity, both referred as "subject".
*
* Its methods should be used to perform role-based authorization.
*
* An instance of this class is generated each time a subject
* successfully logs in Astroboa and is accessible from method
* {@link AstroboaClientContextHolder#getActiveSecurityContext()}.
*
*
* @author Gregory Chomatas (gchomatas@betaconcept.com)
* @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
*
*/
public final class SecurityContext implements Serializable{
/**
*
*/
private static final long serialVersionUID = 4368086721315850622L;
private final String identity;
private final String authenticationToken;
private final Subject subject;
private final int authenticationTokenTimeout;
//List containing all repositories, authenticated user or entity is authorized to access
private List<String> authorizedRepositories;
//List containing all user's roles.
private Set<String> roles;
public SecurityContext(String authenticationToken,
Subject subject, int authenticationTokenTimeout,
List<String> availableRepositories) {
this.authenticationToken = authenticationToken;
this.subject = subject;
this.authenticationTokenTimeout = authenticationTokenTimeout;
this.identity = retrieveIdentityFromSubject();
this.roles = retrieveRolesFromSubject();
this.authorizedRepositories = retrieveAuthorizedRepositoriesFromSubject(availableRepositories);
}
private List<String> retrieveAuthorizedRepositoriesFromSubject(List<String> availableRepositories) {
List<String> authorizedRepositories = new ArrayList<String>();
boolean foundAuthorizedRepositoriesPrincipal = false;
if (subject != null){
Set<Group> subjectGroups = subject.getPrincipals(Group.class);
if (subjectGroups != null){
for (Group group : subjectGroups){
if (group.getName() != null && AstroboaPrincipalName.AuthorizedRepositories.toString().equals(group.getName())){
foundAuthorizedRepositoriesPrincipal = true;
Enumeration groupMembers = group.members();
while (groupMembers.hasMoreElements())
{
Principal groupPrincipal = (Principal) groupMembers.nextElement();
authorizedRepositories.add(groupPrincipal.getName());
}
break;
}
}
}
}
//In cases where no information about authorized repositories
//is provided in Subject, a PERMIT ALL policy is enforced,
//thus available repositories must be known during initialization of this
//context
if (! foundAuthorizedRepositoriesPrincipal){
if (CollectionUtils.isNotEmpty(availableRepositories)){
authorizedRepositories.addAll(availableRepositories);
}
}
return authorizedRepositories;
}
private Set<String> retrieveRolesFromSubject() {
Set<String> roles = new HashSet<String>();
if (subject != null){
Set<Group> groups = subject.getPrincipals(Group.class);
if (groups != null){
for (Group group : groups){
if (group.getName() != null && AstroboaPrincipalName.Roles.toString().equals(group.getName())){
addGroupMembersToRoles(group, roles);
break;
}
}
}
}
return roles;
}
private String retrieveIdentityFromSubject() {
if (subject != null){
Set<IdentityPrincipal> userPrincipals = subject.getPrincipals(IdentityPrincipal.class);
if (CollectionUtils.isEmpty(userPrincipals)){
return "";
}
//Retrieve the first one. Normally it should not have more than one
return userPrincipals.iterator().next().getName();
}
return "";
}
/**
* Convenient method to obtain authenticated user id (usually the username) which normally is provided
* as a {@link IdentityPrincipal principal} in {@link #getSubject() subject}.
*
* @return
* The authenticated user name
*/
public String getIdentity(){
return identity;
}
/**
* Upon successfully connecting to a repository, an authentication
* token is created. This token can be used for further use of Astroboa services
* in order to avoid to authenticate every time.
*
* @return
* Authentication token created upon successful connection to an Astroboa repository
*/
public String getAuthenticationToken(){
return authenticationToken;
}
/**
* Return an instance of authenticated user or entity along with
* its {@link Principal identities}, its roles and any other security related
* information.
*
* <p>
* THIS INSTANCE MUST NOT BE ALTERED IN ANY WAY. If you want to perform changes
* in user's roles or principals, you have to use methods provided by this class.
* To obtain a reference to this class you can call {@link AstroboaClientContextHolder#getActiveSecurityContext()}
* at any point at your code.
* </p>
*
* @return
* Security related information for authenticated user>
*/
public Subject getSubject(){
return subject;
}
/**
* Authentication Token timeout in minutes.
*
* <p>
* It represents the amount of the idle time
* that authentication token is considered valid.
* After that time authentication token is removed.
* </p>
*
*
* @return
*/
public int getAuthenticationTokenTimeout() {
return authenticationTokenTimeout;
}
/**
* Convenient method to retrieve all roles for authenticated user.
*
* This method returns all Roles found under the FIRST principal
* found in Subject with the name {@link AstroboaPrincipalName#Roles}.
*
* If roles are provided in a tree, then this tree is traversed as well
*
* @return
*/
public List<String> getAllRoles() {
return new ArrayList<String>(roles);
}
private void addGroupMembersToRoles(Group group, Set<String> roles) {
Enumeration groupMembers = group.members();
while (groupMembers.hasMoreElements()){
Principal role = (Principal) groupMembers.nextElement();
roles.add(role.getName());
if (role instanceof Group){
addGroupMembersToRoles((Group)role, roles);
}
}
}
/**
* Retrieve a list of all repositories which authenticated user or entity
* is authorized to access.
*
* @return
*/
public List<String> getAuthorizedRepositories() {
return new ArrayList<String>(authorizedRepositories);
}
public boolean hasRole(String role){
return StringUtils.isNotBlank(role) && roles.contains(role);
}
/*
* This method adds a role to an already authenticated user.
* It should be used in rare cases only.
*
* Normally, user's roles should not be altered this way.
*/
public boolean addRole(String role){
if (StringUtils.isBlank(role)){
return false;
}
Set<Group> groups = subject.getPrincipals(Group.class);
boolean roleGroupFound = false;
boolean roleAdded = false;
String nameOfGroupWhichContainsTheRoles = AstroboaPrincipalName.Roles.toString();
if (groups != null){
for (Group group : groups){
if (StringUtils.equals(nameOfGroupWhichContainsTheRoles, group.getName())){
roleGroupFound = true;
final CmsPrincipal rolePrincipal = new CmsPrincipal(role);
if (! group.isMember(rolePrincipal)){
group.addMember(rolePrincipal);
roleAdded = true;
}
break;
}
}
}
if (! roleGroupFound){
Group rolesPrincipal = new CmsGroup(nameOfGroupWhichContainsTheRoles);
rolesPrincipal.addMember(new CmsPrincipal(role));
subject.getPrincipals().add(rolesPrincipal);
roleAdded = true;
}
if (roleAdded){
this.roles.add(role);
}
return roleAdded;
}
/*
* This method removes a role to an already authenticated user.
* It should be used in rare cases only.
*
* Normally, user's roles should not be altered this way.
*/
public boolean removeRole(String role){
if (StringUtils.isBlank(role)){
return false;
}
boolean roleHasBeenRemoved = false;
Set<Group> groups = subject.getPrincipals(Group.class);
if (groups != null){
String nameOfGroupWhichContainsTheRoles = AstroboaPrincipalName.Roles.toString();
for (Group group : groups){
if (StringUtils.equals(nameOfGroupWhichContainsTheRoles, group.getName())){
final CmsPrincipal rolePrincipal = new CmsPrincipal(role);
if (group.isMember(rolePrincipal)){
roleHasBeenRemoved = group.removeMember(rolePrincipal);
break;
}
}
}
}
//remove role from the list as well
if (roleHasBeenRemoved && this.roles.contains(role)){
this.roles.remove(role);
}
return roleHasBeenRemoved;
}
public String toString(){
return " SecurityContext authToken : "+ authenticationToken+
(subject!= null ? " Subject : "+ subject.toString() : "");
}
}