/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.ambari.server.security.authorization;
import java.security.Principal;
import java.util.Collection;
import java.util.Objects;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
/**
* This class is a wrapper for authentication objects to
* provide functionality for resolving login aliases to
* ambari user names.
*/
public final class AmbariAuthentication implements Authentication, UserIdAuthentication {
private final Authentication authentication;
private final Object principalOverride;
private final Integer userId;
public AmbariAuthentication(Authentication authentication, Integer userId) {
this.authentication = authentication;
this.principalOverride = getPrincipalOverride();
this.userId = userId;
}
/**
* Set by an <code>AuthenticationManager</code> to indicate the authorities that the principal has been
* granted. Note that classes should not rely on this value as being valid unless it has been set by a trusted
* <code>AuthenticationManager</code>.
* <p>
* Implementations should ensure that modifications to the returned collection
* array do not affect the state of the Authentication object, or use an unmodifiable instance.
* </p>
*
* @return the authorities granted to the principal, or an empty collection if the token has not been authenticated.
* Never null.
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authentication.getAuthorities();
}
/**
* The credentials that prove the principal is correct. This is usually a password, but could be anything
* relevant to the <code>AuthenticationManager</code>. Callers are expected to populate the credentials.
*
* @return the credentials that prove the identity of the <code>Principal</code>
*/
@Override
public Object getCredentials() {
return authentication.getCredentials();
}
/**
* Stores additional details about the authentication request. These might be an IP address, certificate
* serial number etc.
*
* @return additional details about the authentication request, or <code>null</code> if not used
*/
@Override
public Object getDetails() {
return authentication.getDetails();
}
/**
* The identity of the principal being authenticated. In the case of an authentication request with username and
* password, this would be the username. Callers are expected to populate the principal for an authentication
* request.
* <p>
* The <tt>AuthenticationManager</tt> implementation will often return an <tt>Authentication</tt> containing
* richer information as the principal for use by the application. Many of the authentication providers will
* create a {@code UserDetails} object as the principal.
*
* @return the <code>Principal</code> being authenticated or the authenticated principal after authentication.
*/
@Override
public Object getPrincipal() {
if (principalOverride != null) {
return principalOverride;
}
return authentication.getPrincipal();
}
/**
* Used to indicate to {@code AbstractSecurityInterceptor} whether it should present the
* authentication token to the <code>AuthenticationManager</code>. Typically an <code>AuthenticationManager</code>
* (or, more often, one of its <code>AuthenticationProvider</code>s) will return an immutable authentication token
* after successful authentication, in which case that token can safely return <code>true</code> to this method.
* Returning <code>true</code> will improve performance, as calling the <code>AuthenticationManager</code> for
* every request will no longer be necessary.
* <p>
* For security reasons, implementations of this interface should be very careful about returning
* <code>true</code> from this method unless they are either immutable, or have some way of ensuring the properties
* have not been changed since original creation.
*
* @return true if the token has been authenticated and the <code>AbstractSecurityInterceptor</code> does not need
* to present the token to the <code>AuthenticationManager</code> again for re-authentication.
*/
@Override
public boolean isAuthenticated() {
return authentication.isAuthenticated();
}
/**
* See {@link #isAuthenticated()} for a full description.
* <p>
* Implementations should <b>always</b> allow this method to be called with a <code>false</code> parameter,
* as this is used by various classes to specify the authentication token should not be trusted.
* If an implementation wishes to reject an invocation with a <code>true</code> parameter (which would indicate
* the authentication token is trusted - a potential security risk) the implementation should throw an
* {@link IllegalArgumentException}.
*
* @param isAuthenticated <code>true</code> if the token should be trusted (which may result in an exception) or
* <code>false</code> if the token should not be trusted
* @throws IllegalArgumentException if an attempt to make the authentication token trusted (by passing
* <code>true</code> as the argument) is rejected due to the implementation being immutable or
* implementing its own alternative approach to {@link #isAuthenticated()}
*/
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
authentication.setAuthenticated(isAuthenticated);
}
/**
* Returns the name of this principal.
*
* @return the name of this principal.
*/
@Override
public String getName() {
if (principalOverride != null)
{
if (principalOverride instanceof UserDetails) {
return ((UserDetails) principalOverride).getUsername();
}
return principalOverride.toString();
}
return authentication.getName();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AmbariAuthentication that = (AmbariAuthentication) o;
return Objects.equals(authentication, that.authentication) &&
Objects.equals(principalOverride, that.principalOverride);
}
@Override
public int hashCode() {
return Objects.hash(authentication, principalOverride);
}
/**
* Returns a principal object that is to be used
* to override the original principal object
* returned by the inner {@link #authentication} object.
*
* <p>The purpose of overriding the origin principal is to provide
* and object that resolves the contained user name to ambari user name in case
* the original user name is a login alias.</p>
*
* @return principal override of the original one is of type {@link UserDetails},
* if the original one is a login alias name than the user name the login alias resolves to
* otherwise <code>null</code>
*/
private Object getPrincipalOverride() {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
UserDetails user = (UserDetails)principal;
String usernameOrig = user.getUsername();
String username = AuthorizationHelper.resolveLoginAliasToUserName(usernameOrig);
if (username.equals(usernameOrig))
return null; // create override only original username is a login alias
String userPassword = user.getPassword() != null ? user.getPassword() : "";
principal =
new User(
username,
userPassword,
user.isEnabled(),
user.isAccountNonExpired(),
user.isCredentialsNonExpired(),
user.isAccountNonLocked(),
user.getAuthorities());
} else if ( !(principal instanceof Principal) && principal != null ){
String username = principal.toString();
principal = AuthorizationHelper.resolveLoginAliasToUserName(username);
} else {
principal = null;
}
return principal;
}
@Override
public Integer getUserId() {
return userId;
}
}