org.picketlink.idm.credential.handler.TOTPCredentialHandler Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.picketlink.idm.credential.handler;
import org.picketlink.idm.credential.TOTPCredential;
import org.picketlink.idm.credential.TOTPCredentials;
import org.picketlink.idm.credential.handler.annotations.SupportsCredentials;
import org.picketlink.idm.credential.storage.OTPCredentialStorage;
import org.picketlink.idm.credential.util.CredentialUtils;
import org.picketlink.idm.credential.util.TimeBasedOTP;
import org.picketlink.idm.model.Account;
import org.picketlink.idm.spi.CredentialStore;
import org.picketlink.idm.spi.IdentityContext;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
import static org.picketlink.idm.credential.Credentials.Status;
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_ALGORITHM;
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_DELAY_WINDOW;
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS;
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_NUMBER_DIGITS;
/**
* This particular implementation supports the validation of {@link TOTPCredentials}, and updating {@link
* TOTPCredential} credentials.
*
* @author Shane Bryzak
* @author Pedro Silva
*/
@SupportsCredentials(
credentialClass = {TOTPCredentials.class, TOTPCredential.class},
credentialStorage = OTPCredentialStorage.class)
public class TOTPCredentialHandler extends PasswordCredentialHandler, TOTPCredentials, TOTPCredential> {
public static final String ALGORITHM = "ALGORITHM";
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;
@Override
public void setup(CredentialStore> store) {
super.setup(store);
String algorithm = getConfigurationProperty(store, ALGORITHM, DEFAULT_ALGORITHM);
String intervalSeconds = getConfigurationProperty(store, INTERVAL_SECONDS, "" + DEFAULT_INTERVAL_SECONDS);
String numberDigits = getConfigurationProperty(store, NUMBER_DIGITS, "" + DEFAULT_NUMBER_DIGITS);
String delayWindow = getConfigurationProperty(store, DELAY_WINDOW, "" + DEFAULT_DELAY_WINDOW);
this.totp = new TimeBasedOTP(algorithm, Integer.parseInt(numberDigits), Integer.valueOf(intervalSeconds), Integer.valueOf(delayWindow));
}
@Override
public void validate(final IdentityContext context, final TOTPCredentials credentials, final CredentialStore> store) {
super.validate(context, credentials, store);
if (Status.VALID.equals(credentials.getStatus()) || Status.EXPIRED.equals(credentials.getStatus())) {
if (!isValid(context, credentials, store)) {
credentials.setStatus(Status.INVALID);
credentials.setValidatedAccount(null);
}
}
}
@Override
public void update(IdentityContext context, Account account, TOTPCredential credential, CredentialStore> store, Date effectiveDate, Date expiryDate) {
// if a credential was not provided, updates only the secret.
if (credential.getValue() != null && credential.getValue().length > 0) {
super.update(context, account, credential, store, effectiveDate, expiryDate);
}
OTPCredentialStorage storage = new OTPCredentialStorage();
if (effectiveDate != null) {
storage.setEffectiveDate(effectiveDate);
}
storage.setExpiryDate(expiryDate);
storage.setSecretKey(credential.getSecret());
storage.setDevice(getDevice(credential.getDevice()));
store.storeCredential(context, account, storage);
}
private boolean isValid(final IdentityContext context, final TOTPCredentials credentials, final CredentialStore> store) {
for (OTPCredentialStorage storage : getCredentialStorages(context, credentials, store)) {
String secretKey = storage.getSecretKey();
String token = credentials.getToken();
if (this.totp.validate(token, secretKey.getBytes())) {
return true;
}
}
return false;
}
private List getCredentialStorages(final IdentityContext context, final TOTPCredentials credentials, final CredentialStore> store) {
List storages = store.retrieveCredentials(context, getAccount(context,
credentials.getUsername()), OTPCredentialStorage.class);
for (OTPCredentialStorage storage : new ArrayList(storages)) {
if (!CredentialUtils.isCurrentCredential(storage) || !isDeviceStorage(credentials.getDevice(), storage)) {
storages.remove(storage);
}
}
return storages;
}
/**
* Checks if the given {@link OTPCredentialStorage} references the given device
. A null device
* means that this storage can reference any device and this method will return true.
*
* @param device
* @param storage
* @return
*/
private boolean isDeviceStorage(String device, final OTPCredentialStorage storage) {
return device == null || device.equals(storage.getDevice());
}
private String getDevice(String device) {
if (isNullOrEmpty(device)) {
device = DEFAULT_DEVICE;
}
return device;
}
private String getConfigurationProperty(CredentialStore> store, String key, String defaultValue) {
Object value = store.getConfig().getCredentialHandlerProperties().get(key);
if (value != null) {
return String.valueOf(value);
}
return defaultValue;
}
}