/*
* Copyright 2016 Red Hat, Inc.
*
* 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.jboss.as.security.elytron;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.Subject;
import org.jboss.as.security.plugins.SecurityDomainContext;
import org.wildfly.security.auth.SupportLevel;
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.authz.Attributes;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.authz.MapAttributes;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;
/**
* A {@link org.wildfly.security.auth.server.SecurityRealm} implementation that delegates credential verification to an
* underlying {@link org.jboss.as.security.plugins.SecurityDomainContext}. This realm is exported as a capability by the
* legacy security subsystem by using the {@code elytron-realm} element that is available in the {@code elytron-integration}
* section in the subsystem configuration. The example bellow illustrates how to export a realm for the security domain
* {@code mydomain}:
*
* <pre>
* <subsystem xmlns="urn:jboss:domain:security:3.0">
* <security-domains>
* <security-domain name="mydomain" cache-type="default">
* ...
* </security-domain>
* ...
* </security-domains>
* <elytron-integration>
* <security-realms>
* <elytron-realm name="LegacyRealm" legacy-jaas-config="mydomain"/>
* <security-realms/>
* </elytron-integration>
* ...
* </subsystem>
* </pre>
* <p/>
* The value of the {@code name} attribute is used as the dynamic name of the exported realm. This is the name that must
* be used in the {@code Elytron} subsystem to reference this realm. So, for the above example, an {@code Elytron}
* configuration would look like this:
*
* <pre>
* <subsystem xmlns="urn:wildfly:elytron:1.0">
* <security-domains>
* <security-domain name="ApplicationDomain" default-realm="LegacyRealm">
* <realm name="LegacyRealm"/>
* </security-domain>
* </security-domains>
* ...
* </subsystem>
* </pre>
* <p/>
* The above Elytron security domain can then be used anywhere in the Elytron subsystem (for example, to setup a
* http-authentication-factory).
* </p>
* The {@code legacy-jaas-config} attribute MUST reference a valid legacy JAAS security domain. Failure to do so will result
* in a dependency resolution error that will prevent the realm from being created.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class SecurityDomainContextRealm implements SecurityRealm {
private SecurityDomainContext domainContext;
public SecurityDomainContextRealm(final SecurityDomainContext context) {
this.domainContext = context;
}
@Override
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
return new PicketBoxBasedIdentity(principal);
}
@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 (PasswordGuessEvidence.class.isAssignableFrom(evidenceType)) {
return SupportLevel.SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
private class PicketBoxBasedIdentity implements RealmIdentity {
private final Principal principal;
private Subject jaasSubject;
private PicketBoxBasedIdentity(final Principal principal) {
this.principal = principal;
}
@Override
public Principal getRealmIdentityPrincipal() {
return principal;
}
@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return SecurityDomainContextRealm.this.getCredentialAcquireSupport(credentialType, algorithmName);
}
@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 {
return SecurityDomainContextRealm.this.getEvidenceVerifySupport(evidenceType, algorithmName);
}
@Override
public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
if (domainContext == null || domainContext.getAuthenticationManager() == null) {
throw new RealmUnavailableException();
}
else {
jaasSubject = new Subject();
Object jaasCredential = evidence;
if (evidence instanceof PasswordGuessEvidence) {
jaasCredential = ((PasswordGuessEvidence) evidence).getGuess();
}
return domainContext.getAuthenticationManager().isValid(principal, jaasCredential, jaasSubject);
}
}
@Override
public boolean exists() throws RealmUnavailableException {
return true;
}
@Override
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
Attributes attributes = null;
if (this.jaasSubject != null) {
/* process the JAAS subject, extracting attributes from groups that might have been set in the subject
by the JAAS login modules (e.g. caller principal, roles) */
final Set<Principal> principals = jaasSubject.getPrincipals();
if (principals != null) {
for (Principal principal : principals) {
if (principal instanceof Group) {
final String key = principal.getName();
final Set<String> values = new HashSet<>();
final Enumeration<? extends Principal> enumeration = ((Group) principal).members();
while (enumeration.hasMoreElements()) {
values.add(enumeration.nextElement().getName());
}
if (attributes == null) {
attributes = new MapAttributes();
}
attributes.addAll(key, values);
}
}
}
}
if (attributes == null)
attributes = Attributes.EMPTY;
return AuthorizationIdentity.basicIdentity(attributes);
}
}
}