/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.wildfly.security.auth.realm.token;
import org.wildfly.common.Assert;
import org.wildfly.security._private.ElytronMessages;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.authz.Attributes;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.BearerTokenEvidence;
import org.wildfly.security.evidence.Evidence;
import java.security.Principal;
/**
* <p>A {@link SecurityRealm} capable of building identities based on different security token formats based on a {@link TokenValidator}.
*
* @see TokenValidator
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class TokenSecurityRealm implements SecurityRealm {
private final TokenValidator strategy;
private final String principalClaimName;
/**
* Returns a {@link Builder} instance that can be used to configure and create a {@link TokenSecurityRealm}.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
TokenSecurityRealm(Builder configuration) {
Assert.checkNotNullParam("configuration", configuration);
if (configuration.principalClaimName == null) {
this.principalClaimName = "username";
} else {
this.principalClaimName = configuration.principalClaimName;
}
this.strategy = Assert.checkNotNullParam("tokenValidationStrategy", configuration.strategy);
}
@Override
public RealmIdentity getRealmIdentity(final Evidence evidence) {
return new TokenRealmIdentity(evidence);
}
@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.UNSUPPORTED;
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
if (isBearerTokenEvidence(evidenceType)) {
return SupportLevel.POSSIBLY_SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
private boolean isBearerTokenEvidence(Class<?> evidenceType) {
return evidenceType != null && evidenceType.equals(BearerTokenEvidence.class);
}
final class TokenRealmIdentity implements RealmIdentity {
private final BearerTokenEvidence evidence;
private Attributes claims;
TokenRealmIdentity(Evidence evidence) {
if (evidence != null && isBearerTokenEvidence(evidence.getClass())) {
this.evidence = (BearerTokenEvidence) evidence;
} else {
this.evidence = null;
}
}
@Override
public Principal getRealmIdentityPrincipal() {
try {
if (exists()) {
if (!this.claims.containsKey(principalClaimName)) {
throw ElytronMessages.log.tokenRealmFailedToObtainPrincipalWithClaim(principalClaimName);
}
return new NamePrincipal(this.claims.getFirst(principalClaimName));
}
} catch (Exception e) {
throw ElytronMessages.log.tokenRealmFailedToObtainPrincipal(e);
}
return null;
}
@Override
public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
if (!isBearerTokenEvidence(evidence.getClass())) {
return false;
}
return strategy.validate((BearerTokenEvidence) evidence) != null;
}
@Override
public boolean exists() throws RealmUnavailableException {
return getClaims() != null;
}
@Override
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
if (exists()) {
return new AuthorizationIdentity() {
@Override
public Attributes getAttributes() {
return claims;
}
};
}
return null;
}
@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.UNSUPPORTED;
}
@Override
public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
return null;
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
if (isBearerTokenEvidence(evidenceType)) {
return SupportLevel.SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
private Attributes getClaims() throws RealmUnavailableException {
if (this.claims == null && this.evidence != null) {
this.claims = strategy.validate(this.evidence);
}
return this.claims;
}
}
public static class Builder {
private String principalClaimName = "username";
private TokenValidator strategy;
/**
* Construct a new instance.
*/
private Builder() {
}
/**
* The name of the claim that should be used to obtain the principal's name.
*
* @param name the name of the claim that should be used to obtain the principal's name. Defaults to <code>username</code>
* @return this instance
*/
public Builder principalClaimName(String name) {
this.principalClaimName = name;
return this;
}
/**
* Defines a {@link TokenValidator} that will be used to validate tokens.
*
* @return this instance
*/
public Builder validator(TokenValidator strategy) {
this.strategy = strategy;
return this;
}
/**
* Creates a {@link TokenSecurityRealm} instance with all the given configuration.
*
* @return a new {@link TokenSecurityRealm} instance with all the given configuration
*/
public TokenSecurityRealm build() {
return new TokenSecurityRealm(this);
}
}
}