Skip to content

Commit 73efeca

Browse files
author
Jeff Bornemann
committed
More progress
1 parent 875b576 commit 73efeca

File tree

11 files changed

+320
-72
lines changed

11 files changed

+320
-72
lines changed

src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ class JcrNodesWriter implements ItemWriter<ProtoNode>, ItemWriteListener {
9090
private static void writeToJcr(ProtoNode nodeProto, Session session) {
9191
JcrNodeDecorator jcrNode = ProtoNodeDecorator.createFrom(nodeProto).writeToJcr(session)
9292
jcrNode.setLastModified()
93-
// This will processed all mandatory child nodes only
94-
if(nodeProto.mandatoryChildNodeList && nodeProto.mandatoryChildNodeList.size() > 0) {
95-
for(ProtoNode childNode: nodeProto.mandatoryChildNodeList) {
96-
writeToJcr(childNode, session)
97-
}
98-
}
9993
}
10094

10195
private Session theSession() {

src/main/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecorator.groovy

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@ package com.twcable.grabbit.jcr
33
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
44
import com.twcable.grabbit.security.AuthorizablePrincipal
55
import com.twcable.grabbit.security.InsufficientGrabbitPrivilegeException
6+
import com.twcable.grabbit.util.CryptoUtil
67
import groovy.transform.CompileStatic
78
import groovy.util.logging.Slf4j
9+
import java.lang.reflect.Field
10+
import java.lang.reflect.Method
11+
import java.lang.reflect.ReflectPermission
12+
import javax.annotation.Nonnull
13+
import javax.jcr.Session
814
import org.apache.jackrabbit.api.security.user.Authorizable
15+
import org.apache.jackrabbit.api.security.user.Group
916
import org.apache.jackrabbit.api.security.user.User
1017
import org.apache.jackrabbit.api.security.user.UserManager
1118
import org.apache.jackrabbit.value.StringValue
1219
import org.apache.sling.jcr.base.util.AccessControlUtil
1320

14-
import javax.annotation.Nonnull
15-
import javax.jcr.Session
16-
import java.lang.reflect.Field
17-
import java.lang.reflect.Method
18-
import java.lang.reflect.ReflectPermission
19-
2021
/*
2122
* Copyright 2015 Time Warner Cable, Inc.
2223
*
@@ -59,9 +60,10 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
5960
authorizable = createNewAuthorizable(session)
6061
}
6162
else {
62-
updateAuthorizable(authorizable, session)
63+
authorizable = updateAuthorizable(authorizable, session)
6364
}
64-
session.save()
65+
updateProfile(authorizable, session)
66+
updatePreferences(authorizable, session)
6567
return new JcrNodeDecorator(session.getNode(authorizable.getPath()))
6668
}
6769

@@ -73,7 +75,8 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
7375
final UserManager userManager = getUserManager(session)
7476
if(isUserType()) {
7577
//We set a temporary password for now, and then set the real password later in setPasswordForUser(). See the method for why.
76-
final newUser = userManager.createUser(authorizableID, 'temp', new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
78+
final newUser = userManager.createUser(authorizableID, Long.toString(CryptoUtil.generateNextId()), new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
79+
session.save()
7780
//This is a special protected property for disabling user access
7881
if(hasProperty('rep:disabled')) {
7982
newUser.disable(getStringValueFrom('rep:disabled'))
@@ -83,27 +86,55 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
8386
if(hasProperty(authorizableCategory)) {
8487
newUser.setProperty(authorizableCategory, new StringValue(getStringValueFrom(authorizableCategory)))
8588
}
89+
session.save()
8690
//Special users may not have passwords, such as anonymous users
8791
if(hasProperty('rep:password')) {
8892
setPasswordForUser(newUser, session)
8993
}
9094
return newUser
9195
}
92-
return userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
96+
final Group group = userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
97+
session.save()
98+
return group
9399
}
94100

95101

96102
/**
97-
* From a client API perspective, there is really no way to truely update an existing authorizable node. All of the properties are protected, and there is no
103+
* From a client API perspective, there is really no way to truly update an existing authorizable node. All of the properties are protected, and there is no
98104
* known way to update them. What we do here is essentially remove the existing authorizable as denoted by the authorizableID, and recreate it.
105+
* @return new instance of updated authorizable
99106
*/
100-
private void updateAuthorizable(final Authorizable authorizable, final Session session) {
107+
private Authorizable updateAuthorizable(final Authorizable authorizable, final Session session) {
101108
authorizable.remove()
102109
session.save()
103110
createNewAuthorizable(session)
104111
}
105112

106113

114+
/**
115+
* profile nodes live with authorizables as a way of collecting personal information such as email, and first and last name.
116+
* This gets sent with the authorizable node instead of streamed independently because we do not know the client's new
117+
* authorizable UUID node name at runtime. In other words, authorizables live under different node names from server to server
118+
*/
119+
private void updateProfile(final Authorizable authorizable, final Session session) {
120+
final ProtoNode incomingProfileNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('profile') }
121+
if(!incomingProfileNode) return
122+
createFrom(incomingProfileNode, "${authorizable.getPath()}/profile").writeToJcr(session)
123+
session.save()
124+
}
125+
126+
127+
/**
128+
* @see updateProfile() for a related explanation to this method.
129+
*/
130+
private void updatePreferences(final Authorizable authorizable, final Session session) {
131+
final ProtoNode incomingPreferencesNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('preferences') }
132+
if(!incomingPreferencesNode) return
133+
createFrom(incomingPreferencesNode, "${authorizable.getPath()}/preferences").writeToJcr(session)
134+
session.save()
135+
}
136+
137+
107138
private Authorizable findAuthorizable(final Session session) {
108139
final UserManager userManager = getUserManager(session)
109140
return userManager.getAuthorizable(getAuthorizableID())
@@ -218,9 +249,9 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
218249
Method setPasswordMethod = userManagerDelegateClass.getDeclaredMethod('setPassword', Class.forName('org.apache.jackrabbit.oak.api.Tree', true, userManagerDelegateClass.getClassLoader()), String, String, boolean)
219250
setPasswordMethod.setAccessible(true)
220251
/**
221-
* Step two. We need access to the internal Authorizable object's tree in order to call the internal setPassword method
222-
* User is an instance of org.apache.jackrabbit.oak.jcr.delegate.UserDelegator. We need to get the delegate off of this class's super class org.apache.jackrabbit.oak.jcr.delegate.AuthorizableDelegator
223-
*/
252+
* Step two. We need access to the internal Authorizable object's tree in order to call the internal setPassword method
253+
* User is an instance of org.apache.jackrabbit.oak.jcr.delegate.UserDelegator. We need to get the delegate off of this class's super class org.apache.jackrabbit.oak.jcr.delegate.AuthorizableDelegator
254+
*/
224255
Class authorizableDelegateClass = user.getClass().getSuperclass()
225256
Field authorizableDelegateField = authorizableDelegateClass.getDeclaredField('delegate')
226257
authorizableDelegateField.setAccessible(true)
@@ -230,9 +261,19 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
230261
getTreeMethod.setAccessible(true)
231262

232263
/**
233-
* The last argument where we are passing in 'false' in the secret sauce we need. This parameter is forceHash. As long as forceHash is false, and the password is not
234-
* clear-text, which it isn't since we got it from another Jackrabbit instance, we can set the password as-is.
235-
*/
264+
* The last argument where we are passing in 'false' in the secret sauce we need. This parameter is forceHash. As long as forceHash is false, and the password is not
265+
* clear-text, which it isn't since we got it from another Jackrabbit instance, we can set the password as-is.
266+
*/
236267
setPasswordMethod.invoke(userManagerDelegate, getTreeMethod.invoke(authorizable), getAuthorizableID(), getStringValueFrom('rep:password'), false)
268+
session.save()
269+
}
270+
271+
272+
/**
273+
* An instance wrapper for ease of mocking
274+
* @see ProtoNodeDecorator.createFrom
275+
*/
276+
ProtoNodeDecorator createFrom(final ProtoNode protoNode, final String nameOverride) {
277+
super.createFrom(protoNode, nameOverride)
237278
}
238279
}

src/main/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecorator.groovy

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
3232
@Slf4j
3333
class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
3434

35+
private final String nameOverride
3536

36-
protected DefaultProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection<ProtoPropertyDecorator> protoProperties) {
37+
protected DefaultProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection<ProtoPropertyDecorator> protoProperties, String nameOverride) {
3738
this.innerProtoNode = node
3839
this.protoProperties = protoProperties
40+
this.nameOverride = nameOverride
3941
}
4042

4143

@@ -50,6 +52,12 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
5052
//Then add other properties
5153
writableProperties.each { it.writeToNode(jcrNode) }
5254

55+
if(innerProtoNode.mandatoryChildNodeList && innerProtoNode.mandatoryChildNodeList.size() > 0) {
56+
for(ProtoNode childNode: innerProtoNode.mandatoryChildNodeList) {
57+
createFrom(childNode).writeToJcr(session)
58+
}
59+
}
60+
5361
return new JcrNodeDecorator(jcrNode)
5462
}
5563

@@ -64,14 +72,20 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
6472
}
6573

6674

75+
@Override
76+
String getName() {
77+
nameOverride ?: innerProtoNode.getName()
78+
}
79+
80+
6781
/**
6882
* This method is rather succinct, but helps isolate this JcrUtils static method call
6983
* so that we can get better test coverage.
7084
* @param session to create or get the node path for
7185
* @return the newly created, or found node
7286
*/
7387
JCRNode getOrCreateNode(Session session) {
74-
JcrUtils.getOrCreateByPath(innerProtoNode.name, primaryType.getStringValue(), session)
88+
JcrUtils.getOrCreateByPath(getName(), primaryType.getStringValue(), session)
7589
}
7690

7791

src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import groovy.util.logging.Slf4j
2323
import javax.annotation.Nonnull
2424
import javax.annotation.Nullable
2525
import javax.jcr.Node as JCRNode
26+
import javax.jcr.PathNotFoundException
2627
import javax.jcr.Property as JcrProperty
2728
import javax.jcr.RepositoryException
2829
import javax.jcr.nodetype.ItemDefinition
@@ -40,7 +41,7 @@ class JcrNodeDecorator {
4041
@Delegate
4142
JCRNode innerNode
4243

43-
private Collection<JcrPropertyDecorator> properties
44+
private final Collection<JcrPropertyDecorator> properties
4445

4546
//Evaluated in a lazy fashion
4647
private Collection<JcrNodeDecorator> immediateChildNodes
@@ -49,10 +50,9 @@ class JcrNodeDecorator {
4950
JcrNodeDecorator(@Nonnull JCRNode node) {
5051
if(!node) throw new IllegalArgumentException("node must not be null!")
5152
this.innerNode = node
52-
String primaryType = node.getProperty(JCR_PRIMARYTYPE).string
5353
Collection<JcrProperty> innerProperties = node.properties.toList()
5454
this.properties = innerProperties.collect { JcrProperty property ->
55-
new JcrPropertyDecorator(property, primaryType)
55+
new JcrPropertyDecorator(property, this)
5656
}
5757
}
5858

@@ -85,11 +85,19 @@ class JcrNodeDecorator {
8585

8686
/**
8787
* Identify all required child nodes
88-
* @return list of immediate required child nodes that must exist with this node, or null if no children
88+
* @return list of immediate required child nodes that must be transported with this node, or null if no required nodes
8989
*/
9090
@Nullable
9191
Collection<JcrNodeDecorator> getRequiredChildNodes() {
92-
return hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JcrNodeDecorator childJcrNode -> childJcrNode.isRequiredNode() } : null
92+
Collection<JcrNodeDecorator> requiredNodes
93+
if(isAuthorizableType()) {
94+
requiredNodes = getImmediateChildNodes().findAll { JcrNodeDecorator childJcrNode -> childJcrNode.isAuthorizablePart()}
95+
}
96+
else {
97+
requiredNodes = hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JcrNodeDecorator childJcrNode -> childJcrNode.isRequiredNode() } : null
98+
}
99+
100+
return requiredNodes ?: null
93101
}
94102

95103

@@ -154,6 +162,34 @@ class JcrNodeDecorator {
154162
}
155163

156164

165+
/**
166+
* Authorizable nodes are unique on each server, so associated profiles and preferences need to be sent with.
167+
* @return true if this node is part of an authorizable node conglomerate
168+
*/
169+
boolean isAuthorizablePart() {
170+
final String resourceType
171+
try {
172+
resourceType = getProperty('sling:resourceType')?.string
173+
}
174+
catch(PathNotFoundException ex) {
175+
return false
176+
}
177+
final String name = innerNode.getName()
178+
return (name == 'preferences' && resourceType == 'cq:Preferences') ||
179+
(name == 'profile' && resourceType == 'cq/security/components/profile')
180+
}
181+
182+
183+
String getPrimaryType() {
184+
innerNode.getProperty(JCR_PRIMARYTYPE).string
185+
}
186+
187+
188+
boolean isAuthorizableType() {
189+
return primaryType == 'rep:User' || primaryType == 'rep:Group'
190+
}
191+
192+
157193
Object asType(Class clazz) {
158194
if(clazz == JCRNode) {
159195
return innerNode

src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ class JcrPropertyDecorator {
3737
@Delegate
3838
JCRProperty innerProperty
3939

40-
private final String nodeOwnerPrimaryType
40+
private final JcrNodeDecorator nodeOwner
4141

42-
JcrPropertyDecorator(JCRProperty property, String nodeOwnerPrimaryType) {
42+
JcrPropertyDecorator(JCRProperty property, JcrNodeDecorator nodeOwner) {
4343
this.innerProperty = property
44-
this.nodeOwnerPrimaryType = nodeOwnerPrimaryType
44+
this.nodeOwner = nodeOwner
4545
}
4646

4747
/**
@@ -60,7 +60,7 @@ class JcrPropertyDecorator {
6060
return true
6161
}
6262

63-
if(nodeOwnerPrimaryType in ['rep:User', 'rep:Group']) {
63+
if(nodeOwner.isAuthorizableType()) {
6464
return true
6565
}
6666
else {

src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ abstract class ProtoNodeDecorator {
3232

3333
abstract JcrNodeDecorator writeToJcr(@Nonnull Session session)
3434

35-
static ProtoNodeDecorator createFrom(@Nonnull ProtoNode node) {
35+
static ProtoNodeDecorator createFrom(@Nonnull ProtoNode node, String nameOverride = null) {
3636
if(!node) throw new IllegalArgumentException("node must not be null!")
3737
final protoProperties = node.propertiesList.collect { new ProtoPropertyDecorator(it) }
3838
final primaryType = protoProperties.find { it.primaryType }
3939
if(primaryType.isUserType() || primaryType.isGroupType()) {
4040
return new AuthorizableProtoNodeDecorator(node, protoProperties)
4141
}
42-
return new DefaultProtoNodeDecorator(node, protoProperties)
42+
return new DefaultProtoNodeDecorator(node, protoProperties, nameOverride)
4343
}
4444

4545
boolean hasProperty(String propertyName) {

src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class JcrNodesProcessor implements ItemProcessor<JcrNode, ProtoNode> {
6666
}
6767

6868
// Skip this node because it has already been processed by its parent
69-
if(decoratedNode.isRequiredNode()) {
69+
if(decoratedNode.isRequiredNode() || decoratedNode.isAuthorizablePart()) {
7070
return null
7171
} else {
7272
// Build parent node

0 commit comments

Comments
 (0)