Skip to content
This repository has been archived by the owner on Jun 10, 2019. It is now read-only.

[PLINK-188] - Support different devices and multiple TOTP tokens. #143

Merged
merged 1 commit into from Jun 11, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -33,6 +33,7 @@
public class TOTPCredential extends Password {

private final String secret;
private String device;

public TOTPCredential(String secret) {
this((String) null, secret);
Expand All @@ -52,4 +53,11 @@ public String getSecret() {
return this.secret;
}

public String getDevice() {
return this.device;
}

public void setDevice(String device) {
this.device = device;
}
}
Expand Up @@ -27,6 +27,7 @@
public class TOTPCredentials extends UsernamePasswordCredentials {

private String token;
private String device;

public String getToken() {
return this.token;
Expand All @@ -35,4 +36,12 @@ public String getToken() {
public void setToken(String token) {
this.token = token;
}

public String getDevice() {
return this.device;
}

public void setDevice(String device) {
this.device = device;
}
}
Expand Up @@ -30,6 +30,7 @@ public class OTPCredentialStorage implements CredentialStorage {
private Date expiryDate;

private String secretKey;
private String device;

public OTPCredentialStorage() {

Expand Down Expand Up @@ -63,4 +64,13 @@ public String getSecretKey() {
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

@Stored
public String getDevice() {
return this.device;
}

public void setDevice(String device) {
this.device = device;
}
}
Expand Up @@ -44,22 +44,22 @@
* credentials.
* </p>
* <p>
*
* <p/>
* <p>
* How passwords are encoded can be changed by specifying a configuration option using the <code>PASSWORD_ENCODER</code>. By
* default a SHA-512 encoding is performed.
* </p>
*
* <p/>
* <p>
* Password are always salted before encoding.
* </p>
*
* @author Shane Bryzak
* @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
*/
@SupportsCredentials({ UsernamePasswordCredentials.class, Password.class })
@SupportsCredentials({UsernamePasswordCredentials.class, Password.class})
public class PasswordCredentialHandler<S extends CredentialStore<?>, V extends UsernamePasswordCredentials, U extends Password>
implements CredentialHandler<S, V, U> {
implements CredentialHandler<S, V, U> {

private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG";

Expand Down Expand Up @@ -130,7 +130,7 @@ public void validate(SecurityContext context, V credentials, S store) {

@Override
public void update(SecurityContext context, Agent agent, U password, S store,
Date effectiveDate, Date expiryDate) {
Date effectiveDate, Date expiryDate) {

EncodedPasswordStorage hash = new EncodedPasswordStorage();

Expand All @@ -141,10 +141,7 @@ public void update(SecurityContext context, Agent agent, U password, S store,
hash.setSalt(passwordSalt);
hash.setEncodedHash(this.passwordEncoder.encode(saltPassword(rawPassword, passwordSalt)));
hash.setEffectiveDate(effectiveDate);

if (expiryDate != null) {
hash.setExpiryDate(expiryDate);
}
hash.setExpiryDate(expiryDate);

store.storeCredential(context, agent, hash);
}
Expand Down
Expand Up @@ -19,13 +19,15 @@
package org.picketlink.idm.credential.internal;

import java.util.Date;
import java.util.List;
import org.picketlink.idm.credential.TOTPCredential;
import org.picketlink.idm.credential.TOTPCredentials;
import org.picketlink.idm.credential.spi.annotations.SupportsCredentials;
import org.picketlink.idm.credential.totp.TimeBasedOTP;
import org.picketlink.idm.model.Agent;
import org.picketlink.idm.spi.CredentialStore;
import org.picketlink.idm.spi.SecurityContext;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
import static org.picketlink.idm.credential.Credentials.Status;
import static org.picketlink.idm.credential.totp.TimeBasedOTP.DEFAULT_ALGORITHM;
import static org.picketlink.idm.credential.totp.TimeBasedOTP.DEFAULT_DELAY_WINDOW;
Expand Down Expand Up @@ -58,6 +60,7 @@ public class TOTPCredentialHandler extends PasswordCredentialHandler<CredentialS
public static final String INTERVAL_SECONDS = "INTERVAL_SECONDS";
public static final String NUMBER_DIGITS = "NUMBER_DIGITS";
public static final String DELAY_WINDOW = "DELAY_WINDOW";
public static final String DEFAULT_DEVICE = "DEFAULT_DEVICE";

private TimeBasedOTP totp;

Expand All @@ -79,9 +82,20 @@ public void validate(SecurityContext context, TOTPCredentials credentials, Crede

// password is valid, let's validate the token now
if (Status.VALID.equals(credentials.getStatus())) {
Agent agent = credentials.getValidatedAgent();
OTPCredentialStorage storage = null;

OTPCredentialStorage storage = store.retrieveCurrentCredential(context, agent, OTPCredentialStorage.class);
String device = getDevice(credentials.getDevice());

List<OTPCredentialStorage> storedCredentials = store.retrieveCredentials(context, credentials.getValidatedAgent(), OTPCredentialStorage.class);

for (OTPCredentialStorage storedCredential : storedCredentials) {
if (storedCredential.getDevice().equals(device)
&& CredentialUtils.isCurrentCredential(storedCredential)) {
if (storage == null || storage.getEffectiveDate().compareTo(storedCredential.getEffectiveDate()) <= 0) {
storage = storedCredential;
}
}
}

boolean isValid = false;

Expand Down Expand Up @@ -112,10 +126,19 @@ public void update(SecurityContext context, Agent agent, TOTPCredential credenti
storage.setExpiryDate(expiryDate);

storage.setSecretKey(credential.getSecret());
storage.setDevice(getDevice(credential.getDevice()));

store.storeCredential(context, agent, storage);
}

private String getDevice(String device) {
if (isNullOrEmpty(device)) {
device = DEFAULT_DEVICE;
}

return device;
}

private String getConfigurationProperty(CredentialStore<?> store, String key, String defaultValue) {
Object algorithm = store.getConfig().getCredentialHandlerProperties().get(key);

Expand Down
Expand Up @@ -445,6 +445,7 @@ public <T extends CredentialStorage> List<T> retrieveCredentials(SecurityContext

Property<Object> identityTypeProperty = getConfig().getModelProperty(PropertyType.CREDENTIAL_IDENTITY);
Property<Object> typeProperty = getConfig().getModelProperty(PropertyType.CREDENTIAL_TYPE);
Property<Object> effectiveProperty = getConfig().getModelProperty(PropertyType.CREDENTIAL_EFFECTIVE_DATE);

EntityManager em = getEntityManager(context);

Expand All @@ -460,6 +461,8 @@ public <T extends CredentialStorage> List<T> retrieveCredentials(SecurityContext

criteria.where(predicates.toArray(new Predicate[predicates.size()]));

criteria.orderBy(builder.desc(root.get(effectiveProperty.getName())));

List<?> result = em.createQuery(criteria).getResultList();

List<T> storages = new ArrayList<T>();
Expand Down
Expand Up @@ -68,6 +68,70 @@ public void testSuccessfulValidation() throws Exception {
assertEquals(Status.VALID, credentials.getStatus());
}

@Test
public void testMultipleDevices() throws Exception {
IdentityManager identityManager = getIdentityManager();
User user = createUser("someUser");
TOTPCredential defaultDevice = new TOTPCredential(DEFAULT_PASSWORD, DEFAULT_TOTP_SECRET);

identityManager.updateCredential(user, defaultDevice);

String iphoneSecret = "iphone_secret";
TOTPCredential iphoneDevice = new TOTPCredential(DEFAULT_PASSWORD, iphoneSecret);

String iphoneDeviceName = "My IPhone #SN-121212121";
iphoneDevice.setDevice(iphoneDeviceName);
identityManager.updateCredential(user, iphoneDevice);

String androidSecret = "android_secret";
TOTPCredential androidDevice = new TOTPCredential(DEFAULT_PASSWORD, androidSecret);

String androidDeviceName = "My Android #SN-56757554";
androidDevice.setDevice(androidDeviceName);
identityManager.updateCredential(user, androidDevice);

TOTPCredentials credentials = new TOTPCredentials();

credentials.setUsername(user.getLoginName());
credentials.setPassword(new Password(DEFAULT_PASSWORD));

TimeBasedOTP totp = new TimeBasedOTP();

// validate default device credentials
credentials.setToken(totp.generate(DEFAULT_TOTP_SECRET));
identityManager.validateCredentials(credentials);

assertEquals(Status.VALID, credentials.getStatus());

// validate iphone device credentials
credentials.setToken(totp.generate(iphoneSecret));
credentials.setDevice(iphoneDeviceName);
identityManager.validateCredentials(credentials);

assertEquals(Status.VALID, credentials.getStatus());

// validate android device credentials
credentials.setToken(totp.generate(androidSecret));
credentials.setDevice(androidDeviceName);
identityManager.validateCredentials(credentials);

assertEquals(Status.VALID, credentials.getStatus());

// should fail, trying to use a iphone token in a android device
credentials.setToken(totp.generate(iphoneSecret));
credentials.setDevice(androidDeviceName);
identityManager.validateCredentials(credentials);

assertEquals(Status.INVALID, credentials.getStatus());

// should fail, trying to use a android token in a iphone device
credentials.setToken(totp.generate(androidSecret));
credentials.setDevice(iphoneDeviceName);
identityManager.validateCredentials(credentials);

assertEquals(Status.INVALID, credentials.getStatus());
}

@Test
public void testDelayWindow() throws Exception {
IdentityManager identityManager = getIdentityManager();
Expand Down