Skip to content
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
9 changes: 3 additions & 6 deletions core/src/main/java/dev/vml/es/acm/core/acl/Acl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.vml.es.acm.core.acl;

import com.adobe.granite.crypto.CryptoSupport;
import dev.vml.es.acm.core.acl.authorizable.AclAuthorizable;
import dev.vml.es.acm.core.acl.authorizable.AclGroup;
import dev.vml.es.acm.core.acl.authorizable.AclUser;
Expand All @@ -17,12 +18,8 @@ public class Acl {

private final AclChecker checker;

public Acl(ResourceResolver resourceResolver) {
this(resourceResolver, CommitPolicy.of(resourceResolver, true));
}

public Acl(ResourceResolver resourceResolver, CommitPolicy commitPolicy) {
this.context = new AclContext(resourceResolver, commitPolicy);
public Acl(ResourceResolver resourceResolver, CommitPolicy commitPolicy, CryptoSupport cryptoSupport) {
this.context = new AclContext(resourceResolver, commitPolicy, cryptoSupport);
this.checker = new AclChecker(context);
}

Expand Down
12 changes: 6 additions & 6 deletions core/src/main/java/dev/vml/es/acm/core/acl/AclContext.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package dev.vml.es.acm.core.acl;

import com.adobe.granite.crypto.CryptoSupport;
import dev.vml.es.acm.core.acl.authorizable.AclAuthorizable;
import dev.vml.es.acm.core.acl.authorizable.AclGroup;
import dev.vml.es.acm.core.acl.authorizable.AclUser;
import dev.vml.es.acm.core.acl.utils.AuthorizableManager;
import dev.vml.es.acm.core.acl.utils.CryptoDecryptor;
import dev.vml.es.acm.core.acl.utils.PermissionsManager;
import dev.vml.es.acm.core.repo.CommitPolicy;
import dev.vml.es.acm.core.repo.Repo;
Expand Down Expand Up @@ -33,18 +35,16 @@ public class AclContext {

private final PermissionsManager permissionsManager;

public AclContext(ResourceResolver resourceResolver) {
this(resourceResolver, CommitPolicy.of(resourceResolver, true));
}

public AclContext(ResourceResolver resourceResolver, CommitPolicy commitPolicy) {
public AclContext(ResourceResolver resourceResolver, CommitPolicy commitPolicy, CryptoSupport cryptoSupport) {
try {
JackrabbitSession session = (JackrabbitSession) resourceResolver.adaptTo(Session.class);
UserManager userManager = session.getUserManager();
AccessControlManager accessControlManager = session.getAccessControlManager();
ValueFactory valueFactory = session.getValueFactory();
CryptoDecryptor cryptoDecryptor = new CryptoDecryptor(cryptoSupport);
this.resourceResolver = resourceResolver;
this.authorizableManager = new AuthorizableManager(session, userManager, valueFactory, commitPolicy);
this.authorizableManager =
new AuthorizableManager(session, userManager, valueFactory, commitPolicy, cryptoDecryptor);
this.permissionsManager = new PermissionsManager(session, accessControlManager, valueFactory, commitPolicy);
} catch (RepositoryException e) {
throw new AclException("Cannot access repository while obtaining ACL context!", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,28 @@ public class AuthorizableManager {

private final CommitPolicy commitPolicy;

private final CryptoDecryptor cryptoDecryptor;

public AuthorizableManager(
JackrabbitSession session, UserManager userManager, ValueFactory valueFactory, CommitPolicy commitPolicy) {
JackrabbitSession session,
UserManager userManager,
ValueFactory valueFactory,
CommitPolicy commitPolicy,
CryptoDecryptor cryptoDecryptor) {
this.session = session;
this.userManager = userManager;
this.valueFactory = valueFactory;
this.commitPolicy = commitPolicy;
this.cryptoDecryptor = cryptoDecryptor;
}

public User createUser(String id, String password, String path) {
try {
Principal principal = () -> id;
if (StringUtils.isEmpty(password)) {
password = PasswordUtils.generateRandomPassword();
} else {
password = decrypt(password);
}
User user = userManager.createUser(id, password, principal, path);
save(String.format("creating user '%s'", id));
Expand Down Expand Up @@ -166,8 +175,9 @@ public boolean testPassword(User user, String password) {
String userId = null;
try {
userId = user.getID();
String plainPassword = decrypt(password);
Repository repository = session.getRepository();
Credentials credentials = new SimpleCredentials(userId, password.toCharArray());
Credentials credentials = new SimpleCredentials(userId, plainPassword.toCharArray());
repository.login(credentials).logout();
return true;
} catch (LoginException e) {
Expand All @@ -181,7 +191,7 @@ public void changePassword(User user, String password) {
String userId = null;
try {
userId = user.getID();
user.changePassword(password);
user.changePassword(decrypt(password));
save(String.format("changing password for user '%s'", userId));
} catch (RepositoryException e) {
throw new AclException(String.format("Failed to set password for user '%s'", userId), e);
Expand Down Expand Up @@ -236,4 +246,8 @@ public boolean removeProperty(Authorizable authorizable, String relPath) {
private void save(String context) {
commitPolicy.commit(context);
}

private String decrypt(String text) {
return cryptoDecryptor != null ? cryptoDecryptor.decrypt(text) : text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.vml.es.acm.core.acl.utils;

import com.adobe.granite.crypto.CryptoException;
import com.adobe.granite.crypto.CryptoSupport;
import dev.vml.es.acm.core.acl.AclException;
import org.apache.commons.lang3.StringUtils;

public class CryptoDecryptor {

private final CryptoSupport cryptoSupport;

public CryptoDecryptor(CryptoSupport cryptoSupport) {
this.cryptoSupport = cryptoSupport;
}

public String decrypt(String text) {
if (cryptoSupport == null || StringUtils.isBlank(text) || !cryptoSupport.isProtected(text)) {
return text;
}
try {
return cryptoSupport.unprotect(text);
} catch (CryptoException e) {
throw new AclException("Failed to decrypt text", e);
}
}
}
3 changes: 2 additions & 1 deletion core/src/main/java/dev/vml/es/acm/core/code/CodeContext.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.vml.es.acm.core.code;

import com.adobe.granite.crypto.CryptoSupport;
import dev.vml.es.acm.core.acl.Acl;
import dev.vml.es.acm.core.code.script.ExtensionScript;
import dev.vml.es.acm.core.format.Formatter;
Expand Down Expand Up @@ -46,7 +47,7 @@ public CodeContext(OsgiContext osgiContext, ResourceResolver resourceResolver) {

this.log = LoggerFactory.getLogger(getClass());
this.repo = new Repo(resourceResolver);
this.acl = new Acl(resourceResolver, repo);
this.acl = new Acl(resourceResolver, repo, osgiContext.getService(CryptoSupport.class));
this.activator = new Activator(resourceResolver, osgiContext);
this.formatter = new Formatter();
this.notifier = osgiContext.getService(NotificationManager.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dev.vml.es.acm.core.acl.utils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import com.adobe.granite.crypto.CryptoException;
import com.adobe.granite.crypto.CryptoSupport;
import dev.vml.es.acm.core.acl.AclException;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

class CryptoDecryptorTest {

@Test
void returnsInputWhenCryptoSupportMissing() {
CryptoDecryptor decryptor = new CryptoDecryptor(null);
assertEquals("{abc}", decryptor.decrypt("{abc}"));
}

@Test
void returnsInputWhenTextIsBlank() {
CryptoDecryptor decryptor = new CryptoDecryptor(Mockito.mock(CryptoSupport.class));
assertNull(decryptor.decrypt(null));
assertEquals("", decryptor.decrypt(""));
assertEquals(" ", decryptor.decrypt(" "));
}

@Test
void returnsInputWhenNotProtected() throws CryptoException {
CryptoSupport cryptoSupport = Mockito.mock(CryptoSupport.class);
when(cryptoSupport.isProtected("plainText")).thenReturn(false);
assertEquals("plainText", new CryptoDecryptor(cryptoSupport).decrypt("plainText"));
}

@Test
void unprotectsEncryptedText() throws CryptoException {
CryptoSupport cryptoSupport = Mockito.mock(CryptoSupport.class);
when(cryptoSupport.isProtected("{encrypted}")).thenReturn(true);
when(cryptoSupport.unprotect("{encrypted}")).thenReturn("decrypted");
assertEquals("decrypted", new CryptoDecryptor(cryptoSupport).decrypt("{encrypted}"));
}

@Test
void wrapsFailureInAclException() throws CryptoException {
CryptoSupport cryptoSupport = Mockito.mock(CryptoSupport.class);
when(cryptoSupport.isProtected("{broken}")).thenReturn(true);
doThrow(new CryptoException("hmac mismatch")).when(cryptoSupport).unprotect("{broken}");
AclException exception =
assertThrows(AclException.class, () -> new CryptoDecryptor(cryptoSupport).decrypt("{broken}"));
assertEquals("Failed to decrypt text", exception.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ content: |
acl.createUser { id = "${1:id}"; fullName = "${2:fullName}"; password = "${3:password}"; email = "${4:email}" }
documentation: |
Create or retrieve the user with the given ID, full name, password and email.<br>
The user will only be created if it does not already exist.
The user will only be created if it does not already exist.<br>
Only the <code>password</code> argument is automatically decrypted when wrapped in <code>{...}</code> using CryptoSupport. The encrypted value can be generated via <code>/system/console/crypto</code> (local/on-prem) or <code>cryptoSupport.protect(text)</code> (any AEM — see snippet <code>osgi_crypto_protect</code>). All other arguments remain unchanged.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
group: ACL
name: acl_set
password
name: acl_set_password
content: |
acl.setPassword { userId = "${1:userId}"; password = "${2:password}" }
documentation: |
Set the password for the given user.
Set the password for the given user.<br>
Only the <code>password</code> argument is automatically decrypted when wrapped in <code>{...}</code> using CryptoSupport. The encrypted value can be generated via <code>/system/console/crypto</code> (local/on-prem) or <code>cryptoSupport.protect(text)</code> (any AEM — see snippet <code>osgi_crypto_protect</code>). All other arguments remain unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
group: OSGi
name: osgi_crypto_protect
content: |
osgi.getService(com.adobe.granite.crypto.CryptoSupport.class).protect("${1:plainText}")
documentation: |
Encrypt text with CryptoSupport and get the <code>{...}</code> value accepted by <code>acl.createUser</code> and <code>acl.setPassword</code>.