Skip to content

Commit dc59cbd

Browse files
author
Jeff Bornemann
committed
CR - Write all nodes under authorizables + more coverage to JCRNodeDecorator
1 parent 6d28f58 commit dc59cbd

File tree

11 files changed

+340
-180
lines changed

11 files changed

+340
-180
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jcr_version = 2.0
3333
jms_version = 3.1.1
3434
jsr305_version = 2.0.0
3535
logback_version = 1.0.4
36+
oak_version = 1.2.2
3637
objenesis_version = 2.1
3738
protobuf_gradle_plugin_version = 0.9.1
3839
protobuf_version = 2.6.1

gradle/dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
compile "javax.jcr:jcr:${jcr_version}"
3636
compile "org.apache.jackrabbit:jackrabbit-jcr-commons:${jackrabbit_version}"
3737
compile "org.apache.jackrabbit:jackrabbit-api:${jackrabbit_version}"
38+
compile "org.apache.jackrabbit:oak-core:${oak_version}"
3839
compile "org.apache.sling:org.apache.sling.jcr.api:${sling_commons_version}"
3940

4041
// Logging

gradle/packageExclusions.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ configurations.cq_package {
2929
exclude group: 'org.apache.felix', module: 'org.osgi.compendium'
3030

3131
exclude group: 'org.apache.commons', module: 'commons-lang3'
32-
exclude group: 'org.apache.jackrabbit', module:'jackrabbit-jcr-commons'
33-
exclude group: 'org.apache.jackrabbit', module:'jackrabbit-api'
32+
exclude group: 'org.apache.jackrabbit', module: 'jackrabbit-jcr-commons'
33+
exclude group: 'org.apache.jackrabbit', module: 'jackrabbit-api'
34+
exclude group: 'org.apache.jackrabbit', module: 'oak-core'
3435
exclude group: 'commons-io', module: 'commons-io'
3536

3637
//Exclude Apache Sling Libraries

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

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
5858
}
5959
Authorizable existingAuthorizable = findAuthorizable(session)
6060
Authorizable newAuthorizable = existingAuthorizable ? updateAuthorizable(existingAuthorizable, session) : createNewAuthorizable(session)
61-
updateProfile(newAuthorizable, session)
62-
updatePreferences(newAuthorizable, session)
61+
writeAuthorizablePieces(newAuthorizable, session)
6362
return new JCRNodeDecorator(session.getNode(newAuthorizable.getPath()))
6463
}
6564

@@ -107,25 +106,14 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
107106

108107

109108
/**
110-
* Profile nodes live with authorizables as a way of collecting personal information such as email, and first and last name.
111-
* This gets sent with the authorizable node instead of streamed independently because we do not know the client's new
112-
* authorizable UUID node name at runtime. In other words, authorizables live under different node names from server to server
109+
* 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
110+
* authorizable UUID node name at runtime. In other words, authorizables can live under different node names from server to server
113111
*/
114-
private void updateProfile(final Authorizable authorizable, final Session session) {
115-
final ProtoNode incomingProfileNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('profile') }
116-
if(!incomingProfileNode) return
117-
createFrom(incomingProfileNode, "${authorizable.getPath()}/profile").writeToJcr(session)
118-
session.save()
119-
}
120-
121-
122-
/**
123-
* @see this.updateProfile() for a related explanation to this method.
124-
*/
125-
private void updatePreferences(final Authorizable authorizable, final Session session) {
126-
final ProtoNode incomingPreferencesNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('preferences') }
127-
if(!incomingPreferencesNode) return
128-
createFrom(incomingPreferencesNode, "${authorizable.getPath()}/preferences").writeToJcr(session)
112+
private void writeAuthorizablePieces(final Authorizable authorizable, final Session session) {
113+
innerProtoNode.mandatoryChildNodeList.each {
114+
//We replace the incoming server authorizable path, with the new authorizable path
115+
createFrom(it, it.name.replaceFirst(getName(), authorizable.getPath())).writeToJcr(session)
116+
}
129117
session.save()
130118
}
131119

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
1919
import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
2020
import groovy.transform.CompileStatic
2121
import groovy.util.logging.Slf4j
22-
import org.apache.jackrabbit.commons.JcrUtils
23-
2422
import javax.annotation.Nonnull
2523
import javax.jcr.Node as JCRNode
2624
import javax.jcr.Session
25+
import org.apache.jackrabbit.commons.JcrUtils
26+
2727

2828
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
2929
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
@@ -54,7 +54,8 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
5454

5555
if(innerProtoNode.mandatoryChildNodeList && innerProtoNode.mandatoryChildNodeList.size() > 0) {
5656
for(ProtoNode childNode: innerProtoNode.mandatoryChildNodeList) {
57-
createFrom(childNode).writeToJcr(session)
57+
//Mandatory children must inherit any name overrides from their parent (if they exist)
58+
createFrom(childNode, childNode.getName().replaceFirst(innerProtoNode.name, getName())).writeToJcr(session)
5859
}
5960
}
6061
return new JCRNodeDecorator(jcrNode)

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

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,24 @@ import groovy.transform.CompileStatic
2222
import groovy.util.logging.Slf4j
2323
import javax.annotation.Nonnull
2424
import javax.annotation.Nullable
25+
import javax.jcr.ItemNotFoundException
2526
import javax.jcr.Node as JCRNode
2627
import javax.jcr.PathNotFoundException
2728
import javax.jcr.Property as JcrProperty
2829
import javax.jcr.RepositoryException
2930
import javax.jcr.nodetype.ItemDefinition
31+
import org.apache.jackrabbit.commons.flat.TreeTraverser
3032
import org.apache.jackrabbit.value.DateValue
3133

3234

3335
import static org.apache.jackrabbit.JcrConstants.JCR_CREATED
3436
import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED
3537
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
38+
import static org.apache.jackrabbit.commons.flat.TreeTraverser.ErrorHandler
39+
import static org.apache.jackrabbit.commons.flat.TreeTraverser.InclusionPolicy
40+
import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.ACE_PROPERTY_NAMES
41+
import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.AC_NODETYPE_NAMES
42+
import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.POLICY_NODE_NAMES
3643

3744
@CompileStatic
3845
@Slf4j
@@ -45,6 +52,7 @@ class JCRNodeDecorator {
4552

4653
//Evaluated in a lazy fashion
4754
private Collection<JCRNodeDecorator> immediateChildNodes
55+
private List<JCRNodeDecorator> childNodeList
4856

4957

5058
JCRNodeDecorator(@Nonnull JCRNode node) {
@@ -68,6 +76,19 @@ class JCRNodeDecorator {
6876
}
6977

7078

79+
List<JCRNodeDecorator> getChildNodeList() {
80+
if(!childNodeList) {
81+
childNodeList = (getChildNodeIterator().collect { JCRNode node -> new JCRNodeDecorator(node) } ?: []) as List<JCRNodeDecorator>
82+
}
83+
return childNodeList
84+
}
85+
86+
87+
Iterator<JCRNode> getChildNodeIterator() {
88+
return TreeTraverser.nodeIterator(innerNode, ErrorHandler.IGNORE, new NoRootInclusionPolicy(this))
89+
}
90+
91+
7192
void setLastModified() {
7293
final lastModified = new DateValue(Calendar.instance)
7394
try {
@@ -89,20 +110,13 @@ class JCRNodeDecorator {
89110
*/
90111
@Nullable
91112
Collection<JCRNodeDecorator> getRequiredChildNodes() {
92-
return isAuthorizableType() ? getAuthorizablePieceChildren() : getMandatoryChildren()
113+
if(isAuthorizableType()){
114+
return getChildNodeList().findAll { JCRNodeDecorator childJcrNode -> !childJcrNode.isLoginToken() && !childJcrNode.isACType() }
115+
}
116+
return getMandatoryChildren()
93117
}
94118

95119

96-
/**
97-
* Some child nodes must be transported with authorizables. This is because authorizable nodes have different UUID names from server to server
98-
*/
99-
@Nonnull
100-
private Collection<JCRNodeDecorator> getAuthorizablePieceChildren() {
101-
final Collection<JCRNodeDecorator> authorizableParts = getImmediateChildNodes().findAll { JCRNodeDecorator childJcrNode -> childJcrNode.isAuthorizablePart()}
102-
//We don't send login tokens, because it's not a great idea from a security perspective; and we have no great way of writing them anyway
103-
return authorizableParts.findAll { JCRNodeDecorator childJcrNode -> !childJcrNode.isLoginToken() }
104-
}
105-
106120
/**
107121
* Some nodes must be saved together, per node definition
108122
*/
@@ -174,23 +188,19 @@ class JCRNodeDecorator {
174188

175189

176190
/**
177-
* Authorizable nodes are unique on each server, so associated profiles, preferences, etc need to be sent with.
178-
* @return true if this node is part of an authorizable node conglomerate
191+
* Authorizable nodes can be unique from server to server, so associated profiles, preferences, etc need to be sent with.
192+
* @return true if this node lives under an authorizable
179193
*/
180194
boolean isAuthorizablePart() {
181-
if(isLoginToken()) {
182-
return true
183-
}
184-
final String resourceType
185195
try {
186-
resourceType = getProperty('sling:resourceType')?.string
187-
}
188-
catch(PathNotFoundException ex) {
196+
JCRNodeDecorator parent = new JCRNodeDecorator(getParent())
197+
while(!parent.isAuthorizableType()) {
198+
parent = new JCRNodeDecorator(parent.getParent())
199+
}
200+
return true
201+
} catch(PathNotFoundException | ItemNotFoundException ex) {
189202
return false
190203
}
191-
final String name = innerNode.getName()
192-
return (name == 'preferences' && resourceType == 'cq:Preferences') ||
193-
(name == 'profile' && resourceType == 'cq/security/components/profile')
194204
}
195205

196206

@@ -203,10 +213,14 @@ class JCRNodeDecorator {
203213
return primaryType == 'rep:User' || primaryType == 'rep:Group'
204214
}
205215

216+
boolean isACType() {
217+
(AC_NODETYPE_NAMES + ACE_PROPERTY_NAMES + POLICY_NODE_NAMES).contains(primaryType)
218+
}
219+
206220

207221
boolean isLoginToken() {
208222
final primaryType = getPrimaryType()
209-
return (primaryType == 'rep:Unstructured' && name == '.tokens') || primaryType == 'rep:Token'
223+
return (primaryType == 'rep:Unstructured' && name.tokenize('/')[-1] == '.tokens') || primaryType == 'rep:Token'
210224
}
211225

212226

@@ -218,4 +232,37 @@ class JCRNodeDecorator {
218232
super.asType(clazz)
219233
}
220234
}
235+
236+
@Override
237+
boolean equals(Object obj) {
238+
if (this.is(obj)) return true
239+
if (getClass() != obj.class) return false
240+
241+
JCRNodeDecorator that = (JCRNodeDecorator)obj
242+
243+
return this.hashCode() == that.hashCode()
244+
}
245+
246+
@Override
247+
int hashCode() {
248+
return innerNode.getName().hashCode()
249+
}
250+
251+
@CompileStatic
252+
private static class NoRootInclusionPolicy implements InclusionPolicy<JCRNode> {
253+
254+
final JCRNodeDecorator rootNode
255+
256+
NoRootInclusionPolicy(JCRNode rootNode) {
257+
this.rootNode = new JCRNodeDecorator(rootNode)
258+
}
259+
260+
261+
@Override
262+
boolean include(JCRNode node) {
263+
final JCRNodeDecorator candidateNode = new JCRNodeDecorator(node)
264+
//Don't include the root, and dont' include mandatory nodes as they are held within their parent
265+
return (!rootNode.equals(candidateNode)) && (!candidateNode.isMandatoryNode())
266+
}
267+
}
221268
}

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
@@ -50,7 +50,7 @@ class JcrNodesProcessor implements ItemProcessor<JcrNode, ProtoNode> {
5050
JCRNodeDecorator decoratedNode = new JCRNodeDecorator(jcrNode)
5151

5252
//TODO: Access Control Lists nodes are not supported right now.
53-
if (decoratedNode.path.contains("rep:policy")) {
53+
if (decoratedNode.isACType()) {
5454
log.info "Ignoring current node ${decoratedNode.innerNode}"
5555
return null
5656
}

src/test/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecoratorSpec.groovy

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class AuthorizableProtoNodeDecoratorSpec extends Specification {
4343

4444
AuthorizableProtoNodeDecorator theProtoNodeDecorator(boolean forUser, boolean hasProfile, boolean hasPreferences, Closure configuration = null){
4545
ProtoNodeBuilder nodeBuilder = ProtoNode.newBuilder()
46-
nodeBuilder.setName(forUser ? "user1" : "group1")
46+
nodeBuilder.setName(forUser ? "/home/users/u/user1" : "/home/groups/g/group1")
4747

4848
ProtoProperty primaryTypeProperty = ProtoProperty
4949
.newBuilder()
@@ -91,15 +91,15 @@ class AuthorizableProtoNodeDecoratorSpec extends Specification {
9191
nodeBuilder.addProperties(authorizableCategory)
9292
if(hasPreferences) {
9393
ProtoNode preferenceNode = ProtoNode.newBuilder()
94-
.setName('preferences')
94+
.setName("${nodeBuilder.getName()}/preferences")
9595
.addProperties(simplePrimaryType)
9696
.build()
9797
nodeBuilder.addMandatoryChildNode(preferenceNode)
9898
}
9999
if(hasProfile) {
100100
ProtoNode profileNode = ProtoNode
101101
.newBuilder()
102-
.setName('profile')
102+
.setName("${nodeBuilder.getName()}/profile")
103103
.addProperties(simplePrimaryType)
104104
.build()
105105
nodeBuilder.addMandatoryChildNode(profileNode)
@@ -276,24 +276,24 @@ class AuthorizableProtoNodeDecoratorSpec extends Specification {
276276
when:
277277
final node = Mock(Node) {
278278
getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
279-
it.getString() >> 'rep:Group'
279+
it.getString() >> 'rep:User'
280280
}
281281
getProperties() >> Mock(PropertyIterator) {
282282
it.toList() >> []
283283
}
284284
}
285285
final session = Mock(Session) {
286-
it.getNode('/authpath') >> node
286+
it.getNode('/home/users/u/newuser') >> node
287287
}
288288
final protoNodeDecorator = theProtoNodeDecorator(false, exists, false) {
289289
it.getSecurityManager() >> null
290290
it.getUserManager(session) >> Mock(UserManager) {
291291
it.getAuthorizable('authorizableID') >> Mock(Authorizable)
292292
it.createGroup('authorizableID', _, _) >> Mock(Group) {
293-
it.getPath() >> '/authpath'
293+
it.getPath() >> '/home/users/u/newuser'
294294
}
295295
}
296-
(exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/authpath/profile') >> Mock(ProtoNodeDecorator)
296+
(exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/home/users/u/newuser/profile') >> Mock(ProtoNodeDecorator)
297297
}
298298

299299
then:
@@ -309,24 +309,24 @@ class AuthorizableProtoNodeDecoratorSpec extends Specification {
309309
when:
310310
final node = Mock(Node) {
311311
getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
312-
it.getString() >> 'rep:Group'
312+
it.getString() >> 'rep:User'
313313
}
314314
getProperties() >> Mock(PropertyIterator) {
315315
it.toList() >> []
316316
}
317317
}
318318
final session = Mock(Session) {
319-
it.getNode('/authpath') >> node
319+
it.getNode('/home/users/u/newuser') >> node
320320
}
321321
final protoNodeDecorator = theProtoNodeDecorator(false, false, exists) {
322322
it.getSecurityManager() >> null
323323
it.getUserManager(session) >> Mock(UserManager) {
324324
it.getAuthorizable('authorizableID') >> Mock(Authorizable)
325325
it.createGroup('authorizableID', _, _) >> Mock(Group) {
326-
it.getPath() >> '/authpath'
326+
it.getPath() >> '/home/users/u/newuser'
327327
}
328328
}
329-
(exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/authpath/preferences') >> Mock(ProtoNodeDecorator)
329+
(exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/home/users/u/newuser/preferences') >> Mock(ProtoNodeDecorator)
330330
}
331331

332332
then:

0 commit comments

Comments
 (0)