Return-Path: X-Original-To: apmail-directory-commits-archive@www.apache.org Delivered-To: apmail-directory-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 09E7E18E6A for ; Tue, 2 Jun 2015 18:36:32 +0000 (UTC) Received: (qmail 46337 invoked by uid 500); 2 Jun 2015 18:36:29 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 46146 invoked by uid 500); 2 Jun 2015 18:36:29 -0000 Mailing-List: contact commits-help@directory.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@directory.apache.org Delivered-To: mailing list commits@directory.apache.org Received: (qmail 44195 invoked by uid 99); 2 Jun 2015 18:36:27 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 02 Jun 2015 18:36:27 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 7CBDAE0281; Tue, 2 Jun 2015 18:36:27 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: smckinney@apache.org To: commits@directory.apache.org Date: Tue, 02 Jun 2015 18:37:07 -0000 Message-Id: <0f664631608f4cb8b559e5e1c102939d@git.apache.org> In-Reply-To: <42282bfcb2c04a978270a88f66f866fc@git.apache.org> References: <42282bfcb2c04a978270a88f66f866fc@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [42/51] [partial] directory-fortress-core git commit: FC-109 - rename rbac package to impl http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/ba64d26a/src/main/java/org/apache/directory/fortress/core/impl/GroupDAO.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/directory/fortress/core/impl/GroupDAO.java b/src/main/java/org/apache/directory/fortress/core/impl/GroupDAO.java new file mode 100755 index 0000000..11cfed5 --- /dev/null +++ b/src/main/java/org/apache/directory/fortress/core/impl/GroupDAO.java @@ -0,0 +1,508 @@ +/* + + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.directory.fortress.core.impl; + + +import org.apache.commons.lang.StringUtils; +import org.apache.directory.api.ldap.model.constants.SchemaConstants; +import org.apache.directory.api.ldap.model.cursor.CursorException; +import org.apache.directory.api.ldap.model.cursor.SearchCursor; +import org.apache.directory.api.ldap.model.entry.DefaultEntry; +import org.apache.directory.api.ldap.model.entry.DefaultModification; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.entry.Modification; +import org.apache.directory.api.ldap.model.entry.ModificationOperation; +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; +import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.fortress.core.model.Group; +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.fortress.core.FinderException; +import org.apache.directory.fortress.core.model.ObjectFactory; +import org.apache.directory.fortress.core.UpdateException; +import org.apache.directory.fortress.core.util.Config; +import org.apache.directory.fortress.core.ldap.ApacheDsDataProvider; +import org.apache.directory.fortress.core.model.User; +import org.apache.directory.fortress.core.util.attr.AttrHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.directory.fortress.core.CreateException; +import org.apache.directory.fortress.core.GlobalErrIds; +import org.apache.directory.fortress.core.GlobalIds; +import org.apache.directory.fortress.core.RemoveException; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Contains the Group node for LDAP Directory Information Tree. + * This class is thread safe. + * + * @author Apache Directory Project + */ +final class GroupDAO extends ApacheDsDataProvider +{ + private static final String CLS_NM = GroupDAO.class.getName(); + private static final Logger LOG = LoggerFactory.getLogger( CLS_NM ); + private static final String GROUP_OBJECT_CLASS = "group.objectclass"; + private static final String GROUP_OBJECT_CLASS_IMPL = Config.getProperty( GROUP_OBJECT_CLASS ); + private static final String GROUP_PROTOCOL_ATTR = "group.protocol"; + private static final String GROUP_PROTOCOL_ATTR_IMPL = Config.getProperty( GROUP_PROTOCOL_ATTR ); + private static final String GROUP_PROPERTY_ATTR = "group.properties"; + private static final String GROUP_PROPERTY_ATTR_IMPL = Config.getProperty( GROUP_PROPERTY_ATTR ); + private static final String GROUP_OBJ_CLASS[] = + { SchemaConstants.TOP_OC, GROUP_OBJECT_CLASS_IMPL }; + private static final String[] GROUP_ATRS = + { + SchemaConstants.CN_AT, + SchemaConstants.DESCRIPTION_AT, + GROUP_PROTOCOL_ATTR_IMPL, + GROUP_PROPERTY_ATTR_IMPL, + SchemaConstants.MEMBER_AT }; + + + /** + * Package private default constructor. + */ + GroupDAO() + { + } + + + /** + * @param group + * @throws org.apache.directory.fortress.core.CreateException + * + */ + Group create( Group group ) throws CreateException + { + LdapConnection ld = null; + String nodeDn = getDn( group.getName(), group.getContextId() ); + + try + { + LOG.debug( "create group dn [{}]", nodeDn ); + Entry myEntry = new DefaultEntry( nodeDn ); + myEntry.add( SchemaConstants.OBJECT_CLASS_AT, GROUP_OBJ_CLASS ); + myEntry.add( SchemaConstants.CN_AT, group.getName() ); + myEntry.add( GROUP_PROTOCOL_ATTR_IMPL, group.getProtocol() ); + loadAttrs( group.getMembers(), myEntry, SchemaConstants.MEMBER_AT ); + loadProperties( group.getProperties(), myEntry, GROUP_PROPERTY_ATTR_IMPL, '=' ); + + if ( StringUtils.isNotEmpty( group.getDescription() ) ) + { + myEntry.add( SchemaConstants.DESCRIPTION_AT, group.getDescription() ); + } + + ld = getAdminConnection(); + add( ld, myEntry ); + } + catch ( LdapException e ) + { + String error = "create group node dn [" + nodeDn + "] caught LDAPException=" + e.getMessage(); + throw new CreateException( GlobalErrIds.GROUP_ADD_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + + return group; + } + + + /** + * @param group + * @return + * @throws org.apache.directory.fortress.core.CreateException + * + */ + Group update( Group group ) throws FinderException, UpdateException + { + LdapConnection ld = null; + String nodeDn = getDn( group.getName(), group.getContextId() ); + + try + { + LOG.debug( "update group dn [{}]", nodeDn ); + List mods = new ArrayList(); + + if ( StringUtils.isNotEmpty( group.getDescription() ) ) + { + mods.add( new DefaultModification( + ModificationOperation.REPLACE_ATTRIBUTE, SchemaConstants.DESCRIPTION_AT, group.getDescription() ) ); + } + + if ( StringUtils.isNotEmpty( group.getProtocol() ) ) + { + mods.add( new DefaultModification( + ModificationOperation.REPLACE_ATTRIBUTE, GROUP_PROTOCOL_ATTR_IMPL, group.getProtocol() ) ); + } + + loadAttrs( group.getMembers(), mods, SchemaConstants.MEMBER_AT ); + loadProperties( group.getProperties(), mods, GROUP_PROPERTY_ATTR_IMPL, true, '=' ); + + if ( mods.size() > 0 ) + { + ld = getAdminConnection(); + modify( ld, nodeDn, mods, group ); + } + } + catch ( LdapException e ) + { + String error = "update group node dn [" + nodeDn + "] caught LDAPException=" + e.getMessage(); + throw new UpdateException( GlobalErrIds.GROUP_UPDATE_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + return get( group ); + } + + + Group add( Group group, String key, String value ) throws FinderException, CreateException + { + LdapConnection ld = null; + String nodeDn = getDn( group.getName(), group.getContextId() ); + + try + { + LOG.debug( "add group property dn [{}], key [{}], value [{}]", nodeDn, key, value ); + List mods = new ArrayList(); + mods.add( new DefaultModification( + ModificationOperation.ADD_ATTRIBUTE, GROUP_PROPERTY_ATTR_IMPL, key + "=" + value ) ); + ld = getAdminConnection(); + modify( ld, nodeDn, mods, group ); + } + catch ( LdapException e ) + { + String error = "update group property node dn [" + nodeDn + "] caught LDAPException=" + e.getMessage(); + throw new CreateException( GlobalErrIds.GROUP_ADD_PROPERTY_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + + return get( group ); + } + + + Group delete( Group group, String key, String value ) throws FinderException, RemoveException + { + LdapConnection ld = null; + String nodeDn = getDn( group.getName(), group.getContextId() ); + + try + { + LOG.debug( "delete group property dn [{}], key [{}], value [{}]", nodeDn, key, value ); + List mods = new ArrayList(); + mods.add( new DefaultModification( + ModificationOperation.REMOVE_ATTRIBUTE, GROUP_PROPERTY_ATTR_IMPL, key + "=" + value ) ); + ld = getAdminConnection(); + modify( ld, nodeDn, mods, group ); + } + catch ( LdapException e ) + { + String error = "delete group property node dn [" + nodeDn + "] caught LDAPException=" + e.getMessage(); + throw new RemoveException( GlobalErrIds.GROUP_DELETE_PROPERTY_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + return get( group ); + } + + + /** + * This method will remove group node from diretory. + * + * @param group + * @throws org.apache.directory.fortress.core.RemoveException + * + */ + Group remove( Group group ) throws RemoveException + { + LdapConnection ld = null; + String nodeDn = getDn( group.getName(), group.getContextId() ); + LOG.debug( "remove group dn [{}]", nodeDn ); + try + { + ld = getAdminConnection(); + deleteRecursive( ld, nodeDn ); + } + catch ( CursorException e ) + { + String error = "remove group node dn [" + nodeDn + "] caught CursorException=" + + e.getMessage(); + throw new RemoveException( GlobalErrIds.GROUP_DELETE_FAILED, error, e ); + } + catch ( LdapException e ) + { + String error = "remove group node dn [" + nodeDn + "] caught LDAPException=" + e.getMessage(); + throw new RemoveException( GlobalErrIds.GROUP_DELETE_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + return group; + } + + + /** + * @param entity + * @param userDn + * @return + * @throws org.apache.directory.fortress.core.UpdateException + * + */ + Group assign( Group entity, String userDn ) throws FinderException, UpdateException + { + LdapConnection ld = null; + String dn = getDn( entity.getName(), entity.getContextId() ); + LOG.debug( "assign group property dn [{}], member dn [{}]", dn, userDn ); + try + { + List mods = new ArrayList(); + mods.add( new DefaultModification( + ModificationOperation.ADD_ATTRIBUTE, SchemaConstants.MEMBER_AT, userDn ) ); + ld = getAdminConnection(); + modify( ld, dn, mods, entity ); + } + catch ( LdapException e ) + { + String error = "assign group name [" + entity.getName() + "] user dn [" + userDn + "] caught " + + "LDAPException=" + e.getMessage(); + throw new UpdateException( GlobalErrIds.GROUP_USER_ASSIGN_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + return get( entity ); + } + + + /** + * @param entity + * @param userDn + * @return + * @throws org.apache.directory.fortress.core.UpdateException + * + */ + Group deassign( Group entity, String userDn ) throws FinderException, UpdateException + { + LdapConnection ld = null; + String dn = getDn( entity.getName(), entity.getContextId() ); + LOG.debug( "deassign group property dn [{}], member dn [{}]", dn, userDn ); + + try + { + List mods = new ArrayList(); + mods.add( new DefaultModification( + ModificationOperation.REMOVE_ATTRIBUTE, SchemaConstants.MEMBER_AT, userDn ) ); + + ld = getAdminConnection(); + modify( ld, dn, mods, entity ); + } + catch ( LdapException e ) + { + String error = "deassign group name [" + entity.getName() + "] user dn [" + userDn + "] caught " + + "LDAPException=" + e.getMessage(); + throw new UpdateException( GlobalErrIds.GROUP_USER_DEASSIGN_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + + return get( entity ); + } + + + /** + * @param group + * @return + * @throws org.apache.directory.fortress.core.FinderException + * + */ + Group get( Group group ) throws FinderException + { + Group entity = null; + LdapConnection ld = null; + String dn = getDn( group.getName(), group.getContextId() ); + + try + { + ld = getAdminConnection(); + Entry findEntry = read( ld, dn, GROUP_ATRS ); + if ( findEntry == null ) + { + String warning = "No Group entry found dn [" + dn + "]"; + throw new FinderException( GlobalErrIds.GROUP_NOT_FOUND, warning ); + } + entity = unloadLdapEntry( findEntry, 0 ); + } + catch ( LdapNoSuchObjectException e ) + { + String warning = "read Obj COULD NOT FIND ENTRY for dn [" + dn + "]"; + throw new FinderException( GlobalErrIds.GROUP_NOT_FOUND, warning, e ); + } + catch ( LdapException e ) + { + String error = "read dn [" + dn + "] LdapException=" + e.getMessage(); + throw new FinderException( GlobalErrIds.GROUP_READ_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + return entity; + } + + + /** + * @param group + * @return + * @throws org.apache.directory.fortress.core.FinderException + * + */ + List find( Group group ) throws FinderException + { + List groupList = new ArrayList<>(); + LdapConnection ld = null; + SearchCursor searchResults; + String groupRoot = getRootDn( group.getContextId(), GlobalIds.GROUP_ROOT ); + String filter = null; + + try + { + String searchVal = encodeSafeText( group.getName(), GlobalIds.ROLE_LEN ); + filter = GlobalIds.FILTER_PREFIX + GROUP_OBJECT_CLASS_IMPL + ")(" + SchemaConstants.CN_AT + "=" + searchVal + + "*))"; + ld = getAdminConnection(); + searchResults = search( ld, groupRoot, SearchScope.ONELEVEL, filter, GROUP_ATRS, false, + GlobalIds.BATCH_SIZE ); + long sequence = 0; + while ( searchResults.next() ) + { + groupList.add( unloadLdapEntry( searchResults.getEntry(), sequence++ ) ); + } + } + catch ( CursorException e ) + { + String error = "find filter [" + filter + "] caught CursorException=" + e.getMessage(); + throw new FinderException( GlobalErrIds.GROUP_SEARCH_FAILED, error, e ); + } + catch ( LdapException e ) + { + String error = "find filter [" + filter + "] caught LDAPException=" + e.getMessage(); + throw new FinderException( GlobalErrIds.GROUP_SEARCH_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + + return groupList; + } + + + /** + * @param user + * @return + * @throws org.apache.directory.fortress.core.FinderException + * + */ + List find( User user ) throws FinderException + { + List groupList = new ArrayList<>(); + LdapConnection ld = null; + SearchCursor searchResults; + String groupRoot = getRootDn( user.getContextId(), GlobalIds.GROUP_ROOT ); + String filter = null; + + try + { + encodeSafeText( user.getUserId(), GlobalIds.USERID_LEN ); + filter = GlobalIds.FILTER_PREFIX + GROUP_OBJECT_CLASS_IMPL + ")(" + SchemaConstants.MEMBER_AT + "=" + + user.getDn() + "))"; + ld = getAdminConnection(); + searchResults = search( ld, groupRoot, SearchScope.ONELEVEL, filter, GROUP_ATRS, false, + GlobalIds.BATCH_SIZE ); + long sequence = 0; + + while ( searchResults.next() ) + { + groupList.add( unloadLdapEntry( searchResults.getEntry(), sequence++ ) ); + } + } + catch ( CursorException e ) + { + String error = "find filter [" + filter + "] caught CursorException=" + e.getMessage(); + throw new FinderException( GlobalErrIds.GROUP_SEARCH_FAILED, error, e ); + } + catch ( LdapException e ) + { + String error = "find filter [" + filter + "] caught LDAPException=" + e.getMessage(); + throw new FinderException( GlobalErrIds.GROUP_SEARCH_FAILED, error, e ); + } + finally + { + closeAdminConnection( ld ); + } + + return groupList; + } + + + /** + * @param le + * @param sequence + * @return + * @throws LdapException + */ + private Group unloadLdapEntry( Entry le, long sequence ) + throws LdapInvalidAttributeValueException + { + Group entity = new ObjectFactory().createGroup(); + entity.setName( getAttribute( le, SchemaConstants.CN_AT ) ); + entity.setDescription( getAttribute( le, SchemaConstants.DESCRIPTION_AT ) ); + entity.setProtocol( getAttribute( le, GROUP_PROTOCOL_ATTR_IMPL ) ); + entity.setMembers( getAttributes( le, SchemaConstants.MEMBER_AT ) ); + entity.setMemberDn( true ); + entity.setProperties( AttrHelper.getProperties( getAttributes( le, GROUP_PROPERTY_ATTR_IMPL ), '=' ) ); + entity.setSequenceId( sequence ); + + return entity; + } + + + private String getDn( String name, String contextId ) + { + return SchemaConstants.CN_AT + "=" + name + "," + getRootDn( contextId, GlobalIds.GROUP_ROOT ); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/ba64d26a/src/main/java/org/apache/directory/fortress/core/impl/GroupMgrImpl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/directory/fortress/core/impl/GroupMgrImpl.java b/src/main/java/org/apache/directory/fortress/core/impl/GroupMgrImpl.java new file mode 100755 index 0000000..770c2b9 --- /dev/null +++ b/src/main/java/org/apache/directory/fortress/core/impl/GroupMgrImpl.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.directory.fortress.core.impl; + +import org.apache.directory.fortress.core.GlobalErrIds; +import org.apache.directory.fortress.core.GroupMgr; +import org.apache.directory.fortress.core.ReviewMgr; +import org.apache.directory.fortress.core.ReviewMgrFactory; +import org.apache.directory.fortress.core.SecurityException; +import org.apache.directory.fortress.core.model.Group; +import org.apache.directory.fortress.core.model.User; +import org.apache.directory.fortress.core.util.ObjUtil; + +import java.util.ArrayList; +import java.util.List; + + +/** + * This Manager impl supplies CRUD methods used to manage groups stored within the ldap directory. + * LDAP group nodes are used for utility and security functions within various systems and apps. + *

+ * This class is thread safe. + *

+ + * + * @author Apache Directory Project + */ +public class GroupMgrImpl extends Manageable implements GroupMgr +{ + private static final String CLS_NM = GroupMgrImpl.class.getName(); + private static final GroupP GROUP_P = new GroupP(); + + /** + * Create a new group node. Must have a name and at least one member. + * + * @param group contains {@link org.apache.directory.fortress.core.model.Group}. + * @return {@link org.apache.directory.fortress.core.model.Group} containing entity just added. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + @Override + public Group add( Group group ) throws org.apache.directory.fortress.core.SecurityException + { + String methodName = "add"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + if(!group.isMemberDn()) + { + loadUserDns( group ); + } + + return GROUP_P.add( group ); + } + + /** + * Modify existing group node. The name is required. Does not update members or properties. + * Use {@link GroupMgr#add( Group group, String key, String value )}, {@link GroupMgr#delete( Group group, String key, String value )}, + * {@link GroupMgr#assign( Group group, String member) }, or {@link GroupMgr#deassign( Group group, String member) } for multi-occurring attributes. + * + * @param group contains {@link Group}. + * @return {@link Group} containing entity just modified. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + @Override + public Group update( Group group ) throws SecurityException + { + String methodName = "update"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.update( group ); + } + + /** + * Delete existing group node. The name is required. + * + * @param group contains {@link Group}. + * @return {@link Group} containing entity just removed. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + @Override + public Group delete( Group group ) throws SecurityException + { + String methodName = "delete"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.delete( group ); + } + + /** + * Add a property to an existing group node. The name is required. + * + * @param group contains {@link Group}. + * @param key contains the property key. + * @param value contains contains the property value. + * @return {@link Group} containing entity just modified. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + public Group add( Group group, String key, String value ) throws SecurityException + { + String methodName = "addProperty"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.add( group, key, value ); + } + + /** + * Delete existing group node. The name is required. + * + * @param group contains {@link Group}. + * @param key contains the property key. + * @param value contains contains the property value. + * @return {@link Group} containing entity just modified. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + public Group delete( Group group, String key, String value ) throws SecurityException + { + String methodName = "deleteProperty"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.delete( group, key, value ); + } + + /** + * Read an existing group node. The name is required. + * + * @param group contains {@link Group} with name field set with an existing group name. + * @return {@link Group} containing entity found. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + @Override + public Group read( Group group ) throws SecurityException + { + String methodName = "read"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.read( group ); + } + + /** + * Search using a full or partial group node. The name is required. + * + * @param group contains {@link Group}. + * @return List of type {@link Group} containing entities found. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + @Override + public List find( Group group ) throws SecurityException + { + String methodName = "find"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + + return GROUP_P.search( group ); + } + + /** + * Search for groups by userId. Member (maps to userId) and is required. + * + * @param user contains userId that maps to Group member attribute. + * @return {@link Group} containing entity just added. + * @throws org.apache.directory.fortress.core.SecurityException in the event system error. + */ + public List find( User user ) throws SecurityException + { + String methodName = "findWithUsers"; + assertContext(CLS_NM, methodName, user, GlobalErrIds.USER_NULL); + checkAccess(CLS_NM, methodName); + loadUserDn( user ); + + return GROUP_P.search( user ); + } + + /** + * Assign a user to an existing group node. The name is required and userDn are required. + * + * @param group contains {@link Group}. + * @param member is the relative distinguished name (rdn) of an existing user in ldap. + * @return {@link Group} containing entity to assign. + * @throws org.apache.directory.fortress.core.SecurityException in the event entry already present or other system error. + */ + @Override + public Group assign( Group group, String member ) throws SecurityException + { + String methodName = "assign"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + ReviewMgr reviewMgr = ReviewMgrFactory.createInstance(); + User user = reviewMgr.readUser( new User( member ) ); + + return GROUP_P.assign( group, user.getDn() ); + } + + /** + * Deassign a user from an existing group node. The name is required and userDn are required. + * + * @param group contains {@link Group}. + * @param member is the relative distinguished name (rdn) of an existing user in ldap. + * @return {@link Group} containing entity to deassign + * @throws org.apache.directory.fortress.core.SecurityException in the event entry already present or other system error. + */ + @Override + public Group deassign( Group group, String member ) throws SecurityException + { + String methodName = "deassign"; + assertContext(CLS_NM, methodName, group, GlobalErrIds.GROUP_NULL); + checkAccess(CLS_NM, methodName); + ReviewMgr reviewMgr = ReviewMgrFactory.createInstance(); + User user = reviewMgr.readUser( new User( member ) ); + + return GROUP_P.deassign( group, user.getDn() ); + } + + private void loadUserDns( Group group ) throws SecurityException + { + if( ObjUtil.isNotNullOrEmpty( group.getMembers() )) + { + ReviewMgr reviewMgr = ReviewMgrFactory.createInstance(); + List userDns = new ArrayList(); + + for( String member : group.getMembers() ) + { + User user = reviewMgr.readUser( new User( member ) ); + userDns.add( user.getDn() ); + } + + group.setMembers( userDns ); + } + } + + private void loadUserDn( User inUser ) throws SecurityException + { + ReviewMgr reviewMgr = ReviewMgrFactory.createInstance(); + User outUser = reviewMgr.readUser( inUser ); + inUser.setDn( outUser.getDn() ); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/ba64d26a/src/main/java/org/apache/directory/fortress/core/impl/GroupP.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/directory/fortress/core/impl/GroupP.java b/src/main/java/org/apache/directory/fortress/core/impl/GroupP.java new file mode 100755 index 0000000..47cc241 --- /dev/null +++ b/src/main/java/org/apache/directory/fortress/core/impl/GroupP.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.directory.fortress.core.impl; + + +import org.apache.directory.api.util.Strings; +import org.apache.directory.fortress.core.ValidationException; +import org.apache.directory.fortress.core.model.Group; +import org.apache.directory.fortress.core.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.directory.fortress.core.GlobalErrIds; +import org.apache.directory.fortress.core.GlobalIds; +import org.apache.directory.fortress.core.SecurityException; +import org.apache.directory.fortress.core.model.VUtil; + +import java.util.List; + + +/** + * Process module for the group node of Fortress directory structure. + * This class is thread safe. + * + * @author Apache Directory Project + */ +final class GroupP +{ + private static final String CLS_NM = GroupP.class.getName(); + private static final Logger LOG = LoggerFactory.getLogger( CLS_NM ); + private static GroupDAO gDao = new GroupDAO(); + + + /** + * Add a group node to the Directory Information Tree (DIT). + * + * @param group contains the group entity for target node. + * @throws org.apache.directory.fortress.core.SecurityException + * in event of validation or system error. + */ + Group add( Group group ) throws SecurityException + { + validate( group ); + + return gDao.create( group ); + } + + + /** + * Modify a group node within the Directory Information Tree (DIT). + * + * @param group contains the group entity for target node. + * @throws org.apache.directory.fortress.core.SecurityException + * in event of validation or system error. + */ + Group update( Group group ) throws SecurityException + { + validate( group ); + + return gDao.update( group ); + } + + + /** + * Remove the group node. + * + * @param group contains the group entity for target node. + * @throws SecurityException in event of validation or system error. + */ + Group delete( Group group ) throws SecurityException + { + return gDao.remove( group ); + } + + + /** + * Add a new property to an existing Group + * + * @param group + * @param key + * @param value + * @return + * @throws org.apache.directory.fortress.core.SecurityException + * + */ + Group add( Group group, String key, String value ) throws SecurityException + { + return gDao.add( group, key, value ); + } + + + /** + * Remove an existing property value from an existing Group + * + * @param group + * @param key + * @param value + * @return + * @throws org.apache.directory.fortress.core.SecurityException + * + */ + Group delete( Group group, String key, String value ) throws SecurityException + { + return gDao.delete( group, key, value ); + } + + + /** + * Method will add the "member" attribute on LDAP entry which represents a Group assignment. + * + * @param entity contains the group name targeted. + * @param userDn String contains the dn for the user entry that is being assigned the RBAC Role. + * @return Group containing copy of input data. + * @throws SecurityException in the event of data validation or DAO system error. + */ + Group assign( Group entity, String userDn ) throws SecurityException + { + return gDao.assign( entity, userDn ); + } + + + /** + * Method will remove the "member" attribute on LDAP entry which represents a Group assignment. + * + * @param entity contains the role name targeted. + * @param userDn String contains the dn for the user entry that is being assigned the RBAC Role. + * @return Role containing copy of input data. + * @throws SecurityException in the event of data validation or DAO system error. + */ + Group deassign( Group entity, String userDn ) throws SecurityException + { + return gDao.deassign( entity, userDn ); + } + + + /** + * Return a fully populated Group entity for a given name. If matching record not found a + * SecurityException will be thrown. + * + * @param group contains full group name for entry in directory. + * @return Group entity containing all attributes associated. + * @throws SecurityException in the event not found or DAO search error. + */ + Group read( Group group ) throws SecurityException + { + return gDao.get( group ); + } + + + /** + * Takes a search string that contains full or partial Group name in directory. + * + * @param group contains full or partial name. + * @return List of type Group containing fully populated matching entities. If no records found this will be empty. + * @throws SecurityException in the event of DAO search error. + */ + List search( Group group ) throws SecurityException + { + return gDao.find( group ); + } + + + /** + * Takes a search string that contains full or partial Group name in directory. + * + * @param user contains full dn for existing user. + * @return List of type Group containing fully populated matching entities. If no records found this will be empty. + * @throws SecurityException in the event of DAO search error. + */ + List search( User user ) throws SecurityException + { + return gDao.find( user ); + } + + + /** + * Method will perform simple validations to ensure the integrity of the {@link Group} entity targeted for insertion + * or deletion in directory. + * + * @param entity contains the enum type to validate + * @throws org.apache.directory.fortress.core.SecurityException + * thrown in the event the attribute is null. + */ + private void validate( Group entity ) throws SecurityException + { + if ( Strings.isEmpty( entity.getName() ) ) + { + String error = "validate name validation failed, null or empty value"; + LOG.warn( error ); + throw new ValidationException( GlobalErrIds.GROUP_NAME_NULL, error ); + } + + if ( entity.getName().length() > GlobalIds.OU_LEN ) + { + String name = entity.getName(); + String error = "validate name [" + name + "] invalid length [" + entity.getName().length() + "]"; + LOG.warn( error ); + throw new ValidationException( GlobalErrIds.GROUP_NAME_INVLD, error ); + } + + if ( entity.getProtocol().length() > GlobalIds.OU_LEN ) + { + String error = "validate protocol [" + entity.getProtocol() + "] invalid length [" + entity.getProtocol() + .length() + "]"; + LOG.warn( error ); + throw new ValidationException( GlobalErrIds.GROUP_PROTOCOL_INVLD, error ); + } + + if ( !Strings.isEmpty( entity.getDescription() ) ) + { + VUtil.description( entity.getDescription() ); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/ba64d26a/src/main/java/org/apache/directory/fortress/core/impl/HierUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/directory/fortress/core/impl/HierUtil.java b/src/main/java/org/apache/directory/fortress/core/impl/HierUtil.java new file mode 100755 index 0000000..516c90f --- /dev/null +++ b/src/main/java/org/apache/directory/fortress/core/impl/HierUtil.java @@ -0,0 +1,762 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.directory.fortress.core.impl; + + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.directory.fortress.core.model.Graphable; +import org.apache.directory.fortress.core.model.Hier; +import org.apache.directory.fortress.core.model.Relationship; +import org.apache.directory.fortress.core.util.ObjUtil; +import org.jgrapht.graph.SimpleDirectedGraph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.directory.fortress.core.GlobalErrIds; +import org.apache.directory.fortress.core.SecurityException; +import org.apache.directory.fortress.core.ValidationException; + + +/** + * This utility performs base hierarchical processing using this software JGraphT. + *

+ * It is used to provide hierarchical processing APIs for the following data sets: + *
    + *
  1. RBAC Role relations are stored in {@code cn=Hierarchies,ou=Roles,ou=RBAC} ldap node and cached as singleton in {@link RoleUtil}
  2. + *
  3. ARBAC Admin Role relations are stored in {@code cn=Hierarchies,ou=AdminRoles,ou=ARBAC} ldap node and cached as singleton in {@link AdminRoleUtil}
  4. + *
  5. User Organizational Unit relations are stored in {@code cn=Hierarchies,ou=OS-U,ou=ARBAC} node and cached as {@link org.apache.directory.fortress.core.impl.UsoUtil}
  6. + *
  7. Permission Organizational Unit relations are stored in {@code cn=Hierarchies,ou=OS-P,ou=ARBAC} node and cached as {@link org.apache.directory.fortress.core.impl.PsoUtil}
  8. + *
+ * This class... + *
    + *
  1. manipulates data that is stored as singleton inside other classes with vertices of {@code String}, and edges, as {@link org.apache.directory.fortress.core.model.Relationship}s
  2. + *
  3. utilizes open source library, see JGraphT.
  4. + *
  5. processes general hierarchical data structure i.e. allows multiple inheritance with parents.
  6. + *
  7. constructs and parses simple directed graphs.
  8. + *
+ * Static methods on this class are intended for use by other Fortress classes, and cannot be directly invoked by outside programs. + *

+ * This class is thread safe. + *

+ * + * @author Apache Directory Project + */ +final class HierUtil +{ + /** + * Constants used within this class: + */ + private static final String CLS_NM = HierUtil.class.getName(); + private static final Logger LOG = LoggerFactory.getLogger( CLS_NM ); + private static final String VERTEX = "Vertex"; + + /** A lock used internally to protect the access to the locks map */ + private static final ReadWriteLock getLockLock = new ReentrantReadWriteLock(); + + + /** + * The 'Type' attribute corresponds to what type of hierarchy is being referred to. + */ + static enum Type + { + ROLE, + ARLE, + USO, + PSO + } + + private static final Map synchMap = new HashMap(); + + + /** + * Private constructor + * + */ + private HierUtil() + { + } + + /** + * + * @param contextId + * @param type + * @return + */ + static ReadWriteLock getLock( String contextId, Type type ) + { + String syncKey = getSynchKey( contextId, type ); + + try + { + getLockLock.readLock().lock(); + ReadWriteLock synchObj = synchMap.get( syncKey ); + + if ( synchObj == null ) + { + // Not found, we will create a new one and store it into the map + try + { + getLockLock.readLock().unlock(); + getLockLock.writeLock().lock(); + + // Retry immediately to get the lock from the map, it might have been updated by + // another thread while this thread was blocked on the write lock + synchObj = synchMap.get( syncKey ); + + if ( synchObj == null ) + { + synchObj = new ReentrantReadWriteLock(); + synchMap.put( syncKey, synchObj ); + } + + getLockLock.readLock().lock(); + } + finally + { + getLockLock.writeLock().unlock(); + } + } + + return synchObj; + } + finally + { + getLockLock.readLock().unlock(); + } + } + + + /** + * + * @param contextId + * @param type + * @return + */ + private static String getSynchKey( String contextId, Type type ) + { + return type.toString() + ":" + contextId; + } + + + /** + * This api is used to determine parentage for Hierarchical processing. + * It evaluates three relationship expressions: + *

    + *
  1. If child equals parent
  2. + *
  3. If mustExist true and parent-child relationship exists
  4. + *
  5. If mustExist false and parent-child relationship does not exist
  6. + *
+ * Method will throw {@link org.apache.directory.fortress.core.ValidationException} if rule check fails meaning caller failed validation + * attempt to add/remove hierarchical relationship failed. + * + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param child contains name of child. + * @param parent contains name of parent. + * @param mustExist boolean is used to specify if relationship must be true. + * @throws org.apache.directory.fortress.core.ValidationException + * in the event it fails one of the 3 checks. + */ + static void validateRelationship( SimpleDirectedGraph graph, String child, String parent, + boolean mustExist ) + throws ValidationException + { + // Ensure the two nodes aren't the same: + if ( child.equalsIgnoreCase( parent ) ) + { + String error = "validateRelationship child [" + child + "] same as parent [" + parent + "]"; + throw new ValidationException( GlobalErrIds.HIER_REL_INVLD, error ); + } + Relationship rel = new Relationship( child.toUpperCase(), parent.toUpperCase() ); + // Ensure there is a valid child to parent relationship. + if ( mustExist && !isRelationship( graph, rel ) ) + { + String error = "validateRelationship child [" + child + "] does not have parent [" + parent + "]"; + throw new ValidationException( GlobalErrIds.HIER_REL_NOT_EXIST, error ); + } + // Ensure the child doesn't already have the parent as an ascendant. + else if ( !mustExist && isAscendant( child, parent, graph ) ) + { + String error = "validateRelationship child [" + child + "] already has parent [" + parent + "]"; + throw new ValidationException( GlobalErrIds.HIER_REL_EXIST, error ); + } + // Prevent cycles by making sure the child isn't an ascendant of parent. + else if ( !mustExist && isDescedant( parent, child, graph ) ) + { + String error = "validateRelationship child [" + child + "] is parent of [" + parent + "]"; + throw new ValidationException( GlobalErrIds.HIER_REL_CYCLIC, error ); + } + } + + + /** + * This method Convert from logical, {@code org.jgrapht.graph.SimpleDirectedGraph} to ldap entity, {@link org.apache.directory.fortress.core.model.Hier}. + * The conversion iterates over all edges in the graph and loads the corresponding {@link Relationship} data + * into the ldap entity. The ldap entity stores this data physically in the {@code ftRels} attribute of {@code ftHier} object class. + * + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return reference to hierarchical ldap entity {@link org.apache.directory.fortress.core.model.Hier}. + */ + static Hier toHier( SimpleDirectedGraph graph ) + { + Hier he = new Hier(); + Set eSet = graph.edgeSet(); + for ( Relationship edge : eSet ) + { + he.setRelationship( edge ); + } + return he; + } + + + /** + * This method converts from physical ldap entity format, {@link Hier} to logical {@code org.jgrapht.graph.SimpleDirectedGraph}. + * + * @param hier contains parent-child relationship in preparation to storing in ldap {@code ftRels} attribute of {@code ftHier} object class. + * @return {@code org.jgrapht.graph.SimpleDirectedGraph} containing the vertices of {@code String}, and edges, as {@link Relationship}s that correspond to relational data. + */ + private static SimpleDirectedGraph toGraph( Hier hier ) + { + LOG.debug( "toGraph" ); + SimpleDirectedGraph graph = + new SimpleDirectedGraph<>( Relationship.class ); + List edges = hier.getRelationships(); + if ( edges != null && edges.size() > 0 ) + { + for ( Relationship edge : edges ) + { + String child = edge.getChild(); + String parent = edge.getParent(); + + try + { + graph.addVertex( child ); + graph.addVertex( parent ); + graph.addEdge( child, parent, edge ); + } + catch (java.lang.IllegalArgumentException e) + { + String error = "toGraph child: " + child + " parent: " + parent + " caught IllegalArgumentException=" + e; + LOG.error( error ); + } + + LOG.debug( "toGraph child={}, parent={}", child, parent ); + } + } + return graph; + } + + + /** + * This method is synchronized and adds an edge and its associated vertices to simple directed graph stored in static memory of this process. + * + * @param graph synchronized parameter contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param relation contains parent-child relationship targeted for addition. + * @return {@code org.jgrapht.graph.SimpleDirectedGraph} containing the vertices of {@code String}, and edges, as {@link Relationship}s that correspond to relational data. + */ + private static void addEdge( SimpleDirectedGraph graph, Relationship relation ) + { + LOG.debug( "addEdge" ); + synchronized ( graph ) + { + graph.addVertex( relation.getChild().toUpperCase() ); + graph.addVertex( relation.getParent().toUpperCase() ); + graph.addEdge( relation.getChild().toUpperCase(), relation.getParent().toUpperCase(), relation ); + } + } + + + /** + * This method is synchronized and removes an edge from a simple directed graph stored in static memory of this process. + * + * @param graph synchronized parameter contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param relation contains parent-child relationship targeted for removal. + * @return {@code org.jgrapht.graph.SimpleDirectedGraph} containing the vertices of {@code String}, and edges, as {@link Relationship}s that correspond to relational data. + */ + private static void removeEdge( SimpleDirectedGraph graph, Relationship relation ) + { + LOG.debug( "removeEdge" ); + synchronized ( graph ) + { + graph.removeEdge( relation ); + } + } + + + /** + * Return number of children (direct descendants) a given parent node has. + * + * @param name contains the vertex of graph to gather descendants from. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return int value contains the number of children of a given parent vertex. + */ + static int numChildren( String name, SimpleDirectedGraph graph ) + { + Map vx = new HashMap<>(); + vx.put( VERTEX, name.toUpperCase() ); + return numChildren( vx, graph ); + } + + + /** + * Determine if parent-child relationship exists in supplied digraph. + * + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param rel contains parent and child names. + * @return boolean value. true indicates parent-child relationship exists in digraph. + */ + private static boolean isRelationship( SimpleDirectedGraph graph, Relationship rel ) + { + return graph.containsEdge( rel ); + } + + + /** + * Determine how many children a given parent node has. + * + * @param vertex of parent. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return + */ + private static int numChildren( Map vertex, SimpleDirectedGraph graph ) + { + int numChildren = 0; + try + { + String v = vertex.get( VERTEX ); + if ( v == null ) + { + //log.debug("getDescendants vertex is null"); + return 0; + } + LOG.debug( "hasChildren [{}]", v ); + numChildren = graph.inDegreeOf( v ); + } + catch ( java.lang.IllegalArgumentException e ) + { + // vertex is leaf. + } + return numChildren; + } + + + /** + * Recursively traverse the hierarchical graph and return all of the ascendants of a given node. + * + * @param childName maps to vertex to determine parentage. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are parents of given child. + */ + static Set getAscendants( String childName, SimpleDirectedGraph graph ) + { + Map vx = new HashMap<>(); + // TreeSet will return in sorted order: + // create Set with case insensitive comparator: + Set parents = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); + vx.put( VERTEX, childName.toUpperCase() ); + getAscendants( vx, graph, parents ); + return parents; + } + + + /** + * Utility function recursively traverses a given digraph to build a set of all ascendant names. + * + * @param vertex contains the position of the cursor for traversal of graph. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param ascendants contains the result set of ascendant names. + * @return value contains the vertex of current position. + */ + private static String getAscendants( Map vertex, SimpleDirectedGraph graph, + Set ascendants ) + { + String v = vertex.get( VERTEX ); + if ( v == null ) + { + return null; + } + else if ( graph == null ) + { + return null; + } + LOG.debug( "getAscendants [{}]", v); + Set edges; + try + { + edges = graph.outgoingEdgesOf( v ); + + } + catch ( java.lang.IllegalArgumentException iae ) + { + // vertex is leaf. + return null; + } + for ( Relationship edge : edges ) + { + vertex.put( VERTEX, edge.getParent() ); + ascendants.add( edge.getParent() ); + v = getAscendants( vertex, graph, ascendants ); + } + return v; + } + + + /** + * Recursively traverse the hierarchical graph and return all of the descendants for a given node. + * + * @param parentName maps to vertex to determine parentage. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are children of given parent. + */ + static Set getDescendants( String parentName, SimpleDirectedGraph graph ) + { + Map vx = new HashMap<>(); + // TreeSet will return in sorted order: + // create Set with case insensitive comparator: + Set children = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); + vx.put( VERTEX, parentName.toUpperCase() ); + getDescendants( vx, graph, children ); + return children; + } + + + /** + * Recursively traverse the hierarchical graph and determine child node contains a given parent as one of its ascendants. + * + * @param childName maps to vertex to determine parentage. + * @param parentName maps to vertex to determine parentage. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are children of given parent. + */ + private static boolean isAscendant( String childName, String parentName, + SimpleDirectedGraph graph ) + { + boolean isAscendant = false; + Set ascendants = getAscendants( childName, graph ); + if ( ascendants.contains( parentName ) ) + { + isAscendant = true; + } + return isAscendant; + } + + + /** + * Recursively traverse the hierarchical graph and determine if parent node contains a given child as one of its descendants. + * + * @param childName maps to vertex to determine parentage. + * @param parentName maps to vertex to determine parentage. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are children of given parent. + */ + private static boolean isDescedant( String childName, String parentName, + SimpleDirectedGraph graph ) + { + boolean isDescendant = false; + Set descendants = getDescendants( parentName, graph ); + if ( descendants.contains( childName ) ) + { + isDescendant = true; + } + return isDescendant; + } + + + /** + * Utility function recursively traverses a given digraph to build a set of all descendants names. + * + * @param vertex contains the position of the cursor for traversal of graph. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param descendants contains the result set of names of all descendants of node. + * @return value contains the vertex of current position. + */ + private static String getDescendants( Map vertex, SimpleDirectedGraph graph, + Set descendants ) + { + String v = vertex.get( VERTEX ); + if ( v == null ) + { + // vertex is null + return null; + } + else if ( graph == null ) + { + // graph is null + return null; + } + LOG.debug( "getDescendants [{}]", v); + Set edges; + try + { + edges = graph.incomingEdgesOf( v ); + } + catch ( java.lang.IllegalArgumentException iae ) + { + // vertex is leaf. + return null; + } + for ( Relationship edge : edges ) + { + vertex.put( VERTEX, edge.getChild() ); + descendants.add( edge.getChild() ); + v = getDescendants( vertex, graph, descendants ); + } + return v; + } + + + /** + * Utility function returns a set of all children (direct descendant) names. + * + * @param vertex contains the position of the cursor for traversal of graph. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return value contains the vertex of current position. + */ + static Set getChildren( String vertex, SimpleDirectedGraph graph ) + { + Set descendants = new HashSet<>(); + if ( graph == null ) + { + // graph is null + return null; + } + + LOG.debug( "getChildren [{}]", vertex ); + Set edges; + try + { + edges = graph.incomingEdgesOf( vertex ); + } + catch ( java.lang.IllegalArgumentException iae ) + { + // vertex is leaf. + return null; + } + for ( Relationship edge : edges ) + { + descendants.add( edge.getChild() ); + } + return descendants; + } + + + /** + * Recursively traverse the hierarchical graph and return all of the ascendants of a given node. + * + * @param childName maps to vertex to determine parentage. + * @param parentName points to top most ascendant where traversal must stop. + * @param isInclusive if set to true will include the parentName in the result set. False will not return specified parentName. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are parents of given child. + */ + static Set getAscendants( String childName, String parentName, boolean isInclusive, + SimpleDirectedGraph graph ) + { + Map vx = new HashMap<>(); + // TreeSet will return in sorted order: + // create Set with case insensitive comparator: + Set parents = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); + + vx.put( VERTEX, childName.toUpperCase() ); + getAscendants( vx, graph, parents, parentName, isInclusive ); + return parents; + } + + + /** + * Private utility to recursively traverse the hierarchical graph and return all of the ascendants of a given child node. + * + * @param vertex contains node name and acts as cursor for current location. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param parents contains the result set of parent nodes. + * @param stopName contains the name of node where traversal ends. + * @param isInclusive if set to true will include the parentName in the result set. False will not return specified parentName. + * @return Set of names that are parents of given child. + */ + private static String getAscendants( Map vertex, SimpleDirectedGraph graph, + Set parents, String stopName, boolean isInclusive ) + { + String v = vertex.get( VERTEX ); + if ( v == null ) + { + // vertex is null + return null; + } + else if ( graph == null ) + { + // graph is null + return null; + } + LOG.debug( "getAscendants [{}]", v); + Set edges; + try + { + edges = graph.outgoingEdgesOf( v ); + } + catch ( java.lang.IllegalArgumentException iae ) + { + // vertex is leaf. + return null; + } + for ( Relationship edge : edges ) + { + if ( edge.getParent().equalsIgnoreCase( stopName ) ) + { + if ( isInclusive ) + { + parents.add( edge.getParent() ); + } + break; + } + else + { + vertex.put( VERTEX, edge.getParent() ); + parents.add( edge.getParent() ); + v = getAscendants( vertex, graph, parents, stopName, isInclusive ); + } + } + return v; + } + + + /** + * Private utility to return the parents (direct ascendants) of a given child node. + * + * @param vertex contains node name and acts as cursor for current location. + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @return Set of names that are parents of given child. + */ + static Set getParents( String vertex, SimpleDirectedGraph graph ) + { + Set parents = new HashSet<>(); + if ( graph == null ) + { + // graph is null + return null; + } + LOG.debug( "getParents [{}]", vertex); + Set edges; + try + { + edges = graph.outgoingEdgesOf( vertex ); + } + catch ( java.lang.IllegalArgumentException iae ) + { + // vertex is leaf. + return null; + } + for ( Relationship edge : edges ) + { + parents.add( edge.getParent() ); + } + return parents; + } + + + /** + * This method will retrieve the list of all parent-child relationships for a given node. If the node was not found in + * ldap this method will create a new node and store default data. + * The following ldap nodes are currently storing hierarchical data: + *
    + *
  1. RBAC Role relations are stored in {@code cn=Hierarchies,ou=Roles,ou=RBAC} ldap node and cached as singleton in {@link RoleUtil}
  2. + *
  3. ARBAC Admin Role relations are stored in {@code cn=Hierarchies,ou=AdminRoles,ou=ARBAC} ldap node and cached as singleton in {@link AdminRoleUtil}
  4. + *
  5. User Organizational Unit relations are stored in {@code cn=Hierarchies,ou=OS-U,ou=ARBAC} node and cached as {@link org.apache.directory.fortress.core.impl.UsoUtil}
  6. + *
  7. Permission Organizational Unit relations are stored in {@code cn=Hierarchies,ou=OS-P,ou=ARBAC} node and cached as {@link org.apache.directory.fortress.core.impl.PsoUtil}
  8. + *
+ * + * @param contextId maps to sub-tree in DIT, for example ou=contextId, dc=jts, dc = com. + * @return reference the the Hier result set retrieved from ldap. + */ + static Hier loadHier( String contextId, List descendants ) + { + Hier hier = new Hier(); + if ( ObjUtil.isNotNullOrEmpty( descendants ) ) + { + hier.setContextId( contextId ); + for ( Graphable descendant : descendants ) + { + Set parents = descendant.getParents(); + if ( ObjUtil.isNotNullOrEmpty( parents ) ) + { + for ( String parent : parents ) + { + Relationship relationship = new Relationship(); + relationship.setChild( descendant.getName().toUpperCase() ); + relationship.setParent( parent.toUpperCase() ); + hier.setRelationship( relationship ); + } + } + } + } + return hier; + } + + + /** + * This api allows synchronized access to allow updates to hierarchical relationships. + * Method will update the hierarchical data set and reload the JGraphT simple digraph with latest. + * + * @param graph contains a reference to simple digraph {@code org.jgrapht.graph.SimpleDirectedGraph}. + * @param relationship contains parent-child relationship targeted for addition. + * @param op used to pass the ldap op {@link Hier.Op#ADD}, {@link Hier.Op#MOD}, {@link org.apache.directory.fortress.core.model.Hier.Op#REM} + * @throws org.apache.directory.fortress.core.SecurityException in the event of a system error. + */ + static void updateHier( SimpleDirectedGraph graph, Relationship relationship, Hier.Op op ) + throws SecurityException + { + if ( op == Hier.Op.ADD ) + HierUtil.addEdge( graph, relationship ); + else if ( op == Hier.Op.REM ) + HierUtil.removeEdge( graph, relationship ); + else + throw new SecurityException( GlobalErrIds.HIER_CANNOT_PERFORM, CLS_NM + + "updateHier Cannot perform hierarchical operation" ); + } + + + /** + * Method instantiates a new digraph, {@code org.jgrapht.graph.SimpleDirectedGraph}, using data passed in via + * {@link Hier} entity. + * + * @param hier contains the source data for digraph. + * @return reference to {@code org.jgrapht.graph.SimpleDirectedGraph}. + */ + static SimpleDirectedGraph buildGraph( Hier hier ) + { + SimpleDirectedGraph graph; + LOG.debug( "buildGraph is initializing" ); + if ( hier == null ) + { + String error = "buildGraph detected null hier="; + LOG.error( error ); + return null; + } + graph = toGraph( hier ); + LOG.debug( "buildGraph success to toGraph" ); + LOG.debug( "buildGraph is success" ); + return graph; + } +} http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/ba64d26a/src/main/java/org/apache/directory/fortress/core/impl/Manageable.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/directory/fortress/core/impl/Manageable.java b/src/main/java/org/apache/directory/fortress/core/impl/Manageable.java new file mode 100755 index 0000000..882dc89 --- /dev/null +++ b/src/main/java/org/apache/directory/fortress/core/impl/Manageable.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.directory.fortress.core.impl; + +import org.apache.directory.fortress.core.SecurityException; +import org.apache.directory.fortress.core.ValidationException; +import org.apache.directory.fortress.core.model.FortEntity; +import org.apache.directory.fortress.core.model.Permission; +import org.apache.directory.fortress.core.model.Session; +import org.apache.directory.fortress.core.model.VUtil; + +/** + * Abstract class allows outside clients to manage security and multi-tenant concerns within the Fortress runtime. + * The {@link #setAdmin(org.apache.directory.fortress.core.model.Session)} method allows A/RBAC sessions to be loaded and allows authorization + * to be performed on behalf of the user who is contained within the Session object itself. + * The ARBAC permissions will be checked each time outside client makes calls into Fortress API. + * This interface also allows Fortress clients to operate in a multi-tenant fashion using {@link #setContextId(String)}. + *

+ * Implementers of this abstract class will NOT be thread safe because of instance variables that may be set. + * + * @author Apache Directory Project + */ +public abstract class Manageable implements org.apache.directory.fortress.core.Manageable +{ + // These instance variables are the reason why children of this abstract class will not be thread safe: + protected Session adminSess; + protected String contextId; + + /** + * Use this method to load an administrative user's ARBAC Session object into Manager object will enable authorization to + * be performed on behalf of admin user. Setting Session into this object will enforce ARBAC controls and render this class' + * implementer thread unsafe. + * + * @param session contains a valid Fortress A/RBAC Session object. + */ + public final void setAdmin(Session session) + { + this.adminSess = session; + } + + /** + * Use this method to set the tenant id onto function call into Fortress which allows segregation of data by customer. + * The contextId is used for multi-tenancy to isolate data sets within a particular sub-tree within DIT. + * Setting contextId into this object will render this class' implementer thread unsafe. + * + * @param contextId maps to sub-tree in DIT, for example ou=contextId, dc=jts, dc = com. + */ + public final void setContextId(String contextId) + { + this.contextId = contextId; + } + + + /** + * Set A/RBAC session on entity and perform authorization on behalf of the caller if the {@link #adminSess} is set. + * + * @param className contains the class name. + * @param opName contains operation name. + * @param entity contains {@link org.apache.directory.fortress.core.model.FortEntity} instance. + * @throws org.apache.directory.fortress.core.SecurityException + * in the event of data validation or system error. + */ + protected final void setEntitySession(String className, String opName, FortEntity entity) throws SecurityException + { + entity.setContextId(this.contextId); + if (this.adminSess != null) + { + Permission perm = new Permission(className, opName); + perm.setContextId(this.contextId); + AdminUtil.setEntitySession( this.adminSess, perm, entity, this.contextId ); + } + } + + + /** + * Every Fortress Manager API (e.g. addUser, updateUser, addRole, ...) will perform authorization on behalf of the caller IFF the {@link AuditMgrImpl#adminSess} has been set before invocation. + * + * @param className contains the class name. + * @param opName contains operation name. + * @throws org.apache.directory.fortress.core.SecurityException + * in the event of data validation or system error. + */ + protected final void checkAccess(String className, String opName) throws SecurityException + { + if (this.adminSess != null) + { + Permission perm = new Permission(className, opName); + perm.setContextId(this.contextId); + AdminUtil.checkAccess(this.adminSess, perm, this.contextId); + } + } + + /** + * Method is called by Manager APIs to load contextual information on {@link FortEntity}. + *

+ * The information is used to + *
    + *
  1. Load the administrative User's {@link Session} object into entity. This is used for checking to ensure administrator has privilege to perform administrative operation.
  2. + *
  3. Load the target operation's permission into the audit context. This is used for Fortress audit log stored in OpenLDAP
  4. + *
+ * + * @param className contains the class name. + * @param opName contains operation name. + * @param entity used to pass contextual information through Fortress layers for administrative security checks and audit. + * @throws org.apache.directory.fortress.core.SecurityException + * in the event of data validation or system error. + */ + protected final void setAdminData(String className, String opName, FortEntity entity) + { + if (this.adminSess != null) + { + Permission perm = new Permission(className, opName); + entity.setAdminSession(this.adminSess); + entity.setModCode(AdminUtil.getObjName(perm.getObjName()) + "." + perm.getOpName()); + } + entity.setContextId(this.contextId); + } + + + /** + * Method will throw exception if entity reference is null, otherwise will set the contextId of the tenant onto the supplied entity reference. + * @param className contains the class name of caller. + * @param opName contains operation name of caller. + * @param entity used here to pass the tenant id into the Fortress DAO layer.. + * @param errorCode contains the error id to use if null. + * @throws ValidationException in the event object is null. + */ + protected final void assertContext( String className, String opName, FortEntity entity, int errorCode ) throws ValidationException + { + VUtil.assertNotNull( entity, errorCode, getFullMethodName( className, opName ) ); + entity.setContextId( contextId ); + } + + + /** + * Method will throw exception if entity reference is null, otherwise will set the contextId of the tenant onto the supplied entity reference. + * + * @param methodName contains the full method name of caller. + * @param entity used here to pass the tenant id into the Fortress DAO layer.. + * @param errorCode contains the error id to use if null. + * @throws ValidationException in the event object is null. + */ + protected final void assertContext( String methodName, FortEntity entity, int errorCode ) throws ValidationException + { + VUtil.assertNotNull( entity, errorCode, methodName ); + entity.setContextId( contextId ); + } + + /** + * This method is used to generate log statements and returns the concatenation of class name to the operation name. + * @param className of the caller + * @param opName of the caller + * @return className + '.' + opName + */ + protected final String getFullMethodName(String className, String opName) + { + return className + "." + opName; + } +} \ No newline at end of file