Skip to content

Issue 157 follow refs #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions docs/Running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -180,20 +180,3 @@ Invalid:
]
}
```

=== Syncing Users and Groups

Grabbit has support for syncing users and groups. One *important note* about syncing users is that you must take care to avoid syncing the _admin user_.
Jackrabbit will not allow modification of the admin user, so Grabbit will fail on a job that attempts to do so. You can find the path of your admin user
on your data-warehouse instance or other source instance; and add it as an exclude path as so:

```
pathConfigurations :
-
path : /home/groups
-
path : /home/users
excludePaths:
- k/ki9zhpzfe #Admin user
```

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.twcable.grabbit.client.batch

import com.twcable.grabbit.jcr.JcrUtil
import com.twcable.grabbit.jcr.JCRUtil
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.apache.sling.jcr.api.SlingRepository
Expand Down Expand Up @@ -58,7 +58,7 @@ class ClientBatchJobExecutionListener implements JobExecutionListener {
void beforeJob(JobExecution jobExecution) {
log.debug "SlingRepository : ${slingRepository}"
final clientUsername = jobExecution.jobParameters.getString(ClientBatchJob.CLIENT_USERNAME)
final Session session = JcrUtil.getSession(slingRepository, clientUsername)
final Session session = JCRUtil.getSession(slingRepository, clientUsername)

ClientBatchJobContext.setSession(session)
log.info "Starting job : ${jobExecution}\n\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ package com.twcable.grabbit.client.batch.steps.validation

import com.twcable.grabbit.client.batch.ClientBatchJob
import com.twcable.grabbit.client.batch.ClientBatchJobContext
import com.twcable.grabbit.jcr.JCRUtil
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import javax.jcr.Session
import org.springframework.batch.core.JobExecution
import org.springframework.batch.core.StepExecution
import org.springframework.batch.core.job.flow.FlowExecutionStatus
import org.springframework.batch.core.job.flow.JobExecutionDecider

import javax.jcr.PathNotFoundException
import javax.jcr.RepositoryException
import javax.jcr.Session

/**
* This class serves as a validation gate for jobs in-flight. It should be the first step on the client when running a job to determine
* if the job is job that is safe, and valid to execute.
Expand Down Expand Up @@ -57,29 +55,14 @@ class ValidJobDecider implements JobExecutionDecider {
@Override
FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
String jobPath = jobExecution.jobParameters.getString(ClientBatchJob.PATH)
//For processing, remove trailing /
jobPath = jobPath.replaceFirst(/\/$/, '')
//Get the parent's path (if applicable) and determine if it exists already
final parts = jobPath.split('/')
//No parent, so nothing to worry about
if(parts.length <= 2) return JOB_VALID

final parentPath = parts[0..-2].join('/')
final Session session = theSession()
try {
session.getNode(parentPath)
} catch(PathNotFoundException pathException) {
log.warn "${jobPath} is not a valid job path. Make sure a parent is synched or created before this job is run"
log.debug pathException.toString()
return JOB_INVALID
if(JCRUtil.writingToExistingNode(jobPath, theSession())) {
log.debug "${ValidJobDecider.class.canonicalName} Job determined to be valid for job path ${jobPath}"
return JOB_VALID
}
catch(RepositoryException repoException) {
log.error "${RepositoryException.class.canonicalName} Something went wrong when accessing the repository at ${this.class.canonicalName} for job path ${jobPath}!"
log.error repoException.toString()
else {
log.warn "${jobPath} is not a valid job path. Make sure a parent is synched or created before this job is run"
return JOB_INVALID
}
log.debug "${ValidJobDecider.class.canonicalName} Job determined to be valid for job path ${jobPath}"
return JOB_VALID
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class ACLProtoNodeDecorator extends ProtoNodeDecorator {
final String principalName = getPrincipalName(aceNode)
Principal principal = getPrincipal(session, principalName)
if(principal == null) {
log.warn "Principal for name ${principalName} does not exist, or is not accessible. Can not write ACE/ACL information."
log.warn "Principal for name ${principalName} does not exist, or is not accessible. Can not write ACE/ACL information for this principal on ${getParentPath()}. If this principal is currently being synched, it may not be accessible."
return
}
Privilege[] privileges = getPrivilegeNames(aceNode).collect { String privilegeName ->
Expand Down Expand Up @@ -166,7 +166,7 @@ class ACLProtoNodeDecorator extends ProtoNodeDecorator {
final ProtoPropertyDecorator privilegeProperty = node.propertiesList.collect { new ProtoPropertyDecorator(it) }.find { ProtoPropertyDecorator property ->
property.isPrivilege()
}
return privilegeProperty.getPropertyValues().collect { Value value -> value.string }.toArray() as String[]
return privilegeProperty.getStringValues().toArray() as String[]
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import groovy.util.logging.Slf4j
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.ReflectPermission
import java.util.regex.Pattern
import javax.annotation.Nonnull
import javax.jcr.Session
import org.apache.jackrabbit.api.security.user.Authorizable
Expand All @@ -35,7 +34,6 @@ import org.apache.jackrabbit.api.security.user.UserManager
import org.apache.jackrabbit.value.StringValue
import org.apache.sling.jcr.base.util.AccessControlUtil


/**
* This class wraps a serialized node that represents an Authorizable. Authorizables are special system protected nodes, that can only be written under certain
* trees, and can not be written directly by a client.
Expand All @@ -57,10 +55,14 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
throw new InsufficientGrabbitPrivilegeException("JVM Permissions needed by Grabbit to sync Users/Groups were not found. See log for specific permissions needed, and add these to your security manager; or do not sync users and groups." +
"Unfortunately, the way Jackrabbit goes about certain things requires us to do a bit of hacking in order to sync Authorizables securely, and efficiently.")
}
Authorizable existingAuthorizable = findAuthorizable(session)
//the administrator is a special user that Jackrabbit will not let us mess with.
if(getAuthorizableID() == 'admin') {
return new JCRNodeDecorator(session.getNode(findAuthorizable(session, 'admin').getPath()))
}

Authorizable existingAuthorizable = findAuthorizable(session, getAuthorizableID())
Authorizable newAuthorizable = existingAuthorizable ? updateAuthorizable(existingAuthorizable, session) : createNewAuthorizable(session)
writeAuthorizablePieces(newAuthorizable, session)
return new JCRNodeDecorator(session.getNode(newAuthorizable.getPath()))
return new JCRNodeDecorator(session.getNode(newAuthorizable.getPath()), getID())
}


Expand All @@ -86,11 +88,20 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
setPasswordForUser(newUser, session)
}
session.save()
writeMandatoryPieces(session, newUser.getPath())
return newUser
}
final Group group = userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getParentPath())
final Group newGroup = userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getParentPath())
/*
* Write all mandatory pieces, and find those that are authorizables. We then need to see if any of them have membership in this group, and add them.
*/
final Collection<JCRNodeDecorator> authorizablePieces = writeMandatoryPieces(session, newGroup.getPath()).findAll { it.isAuthorizableType() }
final Collection<JCRNodeDecorator> members = authorizablePieces.findAll { getMembershipIDs().contains(it.getTransferredID()) }
members.each { JCRNodeDecorator member ->
newGroup.addMember(member as Authorizable)
}
session.save()
return group
return newGroup
}


Expand All @@ -100,28 +111,25 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
* @return new instance of updated authorizable
*/
private Authorizable updateAuthorizable(final Authorizable authorizable, final Session session) {
//We get all the declared groups of this authorizable so that we can add them back to the new, updated authorizable
final Collection<Group> declaredGroups = authorizable.declaredMemberOf().toList()
for(Group group : declaredGroups) {
group.removeMember(authorizable)
}
authorizable.remove()
session.save()
createNewAuthorizable(session)
}


/**
* Authorizable pieces (nodes that live under Authorizables - profile, preferences, etc) get sent with the authorizable node instead of streamed independently because we do not know the client's new
* authorizable UUID node name at runtime. In other words, authorizables can live under different node names from server to server
*/
private void writeAuthorizablePieces(final Authorizable authorizable, final Session session) {
innerProtoNode.mandatoryChildNodeList.each {
//We replace the incoming server authorizable path, with the new authorizable path
createFrom(it, it.name.replaceFirst(Pattern.quote(getName()), authorizable.getPath())).writeToJcr(session)
final Authorizable newAuthorizable = createNewAuthorizable(session)
for(Group group: declaredGroups) {
group.addMember(newAuthorizable)
}
session.save()
return newAuthorizable
}


private Authorizable findAuthorizable(final Session session) {
private Authorizable findAuthorizable(final Session session, final String authorizableID) {
final UserManager userManager = getUserManager(session)
return userManager.getAuthorizable(getAuthorizableID())
return userManager.getAuthorizable(authorizableID)
}


Expand All @@ -135,6 +143,11 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
}


private Collection<String> getMembershipIDs() {
return hasProperty('rep:members') ? getStringValuesFrom('rep:members') : []
}


/**
* Some JVM's have a SecurityManager set, which based on configuration, can potentially inhibit our hack {@code setPasswordForUser(User, Session)} from working.
* We need to check security permissions before proceeding
Expand Down Expand Up @@ -245,13 +258,4 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
*/
setPasswordMethod.invoke(userManagerDelegate, getTreeMethod.invoke(authorizable), getAuthorizableID(), getStringValueFrom('rep:password'), false)
}


/**
* An instance wrapper for ease of mocking
* @see super.createFrom
*/
ProtoNodeDecorator createFrom(final ProtoNode protoNode, final String nameOverride) {
super.createFrom(protoNode, nameOverride)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import java.util.regex.Pattern
import javax.annotation.Nonnull
import javax.jcr.Node as JCRNode
import javax.jcr.Session
import javax.jcr.Value
import org.apache.jackrabbit.commons.JcrUtils
import org.apache.jackrabbit.value.StringValue


import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
Expand All @@ -49,16 +50,34 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
if(mixinProperty) {
addMixins(mixinProperty, jcrNode)
}
//Then add other properties
writableProperties.each { it.writeToNode(jcrNode) }

if(innerProtoNode.mandatoryChildNodeList && innerProtoNode.mandatoryChildNodeList.size() > 0) {
for(ProtoNode childNode: innerProtoNode.mandatoryChildNodeList) {
//Mandatory children must inherit any name overrides from their parent (if they exist)
createFrom(childNode, childNode.getName().replaceFirst(Pattern.quote(innerProtoNode.name), getName())).writeToJcr(session)
final Collection<ProtoPropertyDecorator> referenceTypeProperties = writableProperties.findAll { it.isReferenceType() }
final Collection<ProtoPropertyDecorator> nonReferenceTypeProperties = writableProperties.findAll { !it.isReferenceType() }

//These can be written now. Reference properties can be written after we write the referenced nodes
nonReferenceTypeProperties.each { it.writeToNode(jcrNode) }

final Collection<JCRNodeDecorator> referenceables = writeMandatoryPieces(session, getName()).findAll { it.isReferenceable() }

/*
* Nodes that are referenced from reference properties are written above in writeMandatoryPieces(). We can now map each
* reference pointer to a transferred id from a node above, and write the pointer with a mapped nodes new identifier.
* The transferred id is what the identifier was on sending server, and the current identifier is what it is now that it is
* written to this server. Identifiers only apply to referenceable nodes (i.e nodes with mix:referenceable)
*/
referenceTypeProperties.each { ProtoPropertyDecorator property ->
final Collection<Value> newReferenceIDValues = property.getStringValues().findResults { String referenceID ->
final JCRNodeDecorator match = referenceables.find { it.transferredID == referenceID }
if(match) {
return new StringValue(match.getIdentifier())
}
} as Collection<Value>
if(!newReferenceIDValues.isEmpty()) {
property.writeToNode(jcrNode, (newReferenceIDValues.toArray() as Value[]))
}
}
return new JCRNodeDecorator(jcrNode)

return new JCRNodeDecorator(jcrNode, getID())
}


Expand Down
Loading