Skip to content

Commit

Permalink
Merge AuthenticationContex into Authentication
Browse files Browse the repository at this point in the history
This PR removes the AuthenticationContext class introduced in elastic#80926 and
merges its functions into Authentication.

It becomes more apparent that the most useful refactoring in elastic#80926 is
the new Subject class, which is also what AuthenticationContext provides
most of its value. The AuthenticationContext is essentially just a thin
wrapper of two subjects which represents the existing Authentication
object in a more structured format. The original plan was to replace
Authentication with AuthenticationContext. However, it has practical
challenges that the usage of Authentication is too wide spread. It's
hard to have a series of scoped changes to replace it. Therefore the new
plan is to stick with Authentication, agumenting it with subjects
similar to what AuthenticationContext has and remove
AuthenticationContext. This PR also deprecates methods that should be
replaced by methods of Subjects. In future, the plan is to remove the
deprecated methods, also rework the User class so it does not need
nest another User to represent run-as (which is another main reason for
the original refactor elastic#80926). Overall, the new plan makes it easier to
spread the work in a few more tightly scoped PRs while achieving the
same original goal.
  • Loading branch information
ywangd committed Mar 23, 2022
1 parent d3fac0e commit 7e79110
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public class Authentication implements ToXContentObject {
private final AuthenticationType type;
private final Map<String, Object> metadata; // authentication contains metadata, includes api_key details (including api_key metadata)

private Subject authenticatingSubject;
private Subject effectiveSubject;

public Authentication(User user, RealmRef authenticatedBy, RealmRef lookedUpBy) {
this(user, authenticatedBy, lookedUpBy, Version.CURRENT, AuthenticationType.REALM, Collections.emptyMap());
}
Expand Down Expand Up @@ -109,10 +112,44 @@ public Authentication(StreamInput in) throws IOException {
this.assertDomainAssignment();
}

/**
* Get the {@link Subject} that performs the actual authentication. This normally means it provides a credentials.
*/
public Subject getAuthenticatingSubject() {
initializeSubjects();
return authenticatingSubject;
}

/**
* Get the {@link Subject} that the authentication effectively represents. It may not be the authenticating subject
* because the authentication subject can run-as another subject.
*/
public Subject getEffectiveSubject() {
initializeSubjects();
return effectiveSubject;
}

/**
* Whether the authentication contains a subject run-as another subject. That is, the authentication subject
* is different from the effective subject.
*/
public boolean isRunAs() {
initializeSubjects();
return authenticatingSubject != effectiveSubject;
}

/**
* Use {@code getEffectiveSubject().getUser()} instead.
*/
@Deprecated
public User getUser() {
return user;
}

/**
* Use {@code getAuthenticatingSubject().getRealm()} instead.
*/
@Deprecated
public RealmRef getAuthenticatedBy() {
return authenticatedBy;
}
Expand All @@ -124,7 +161,10 @@ public RealmRef getLookedUpBy() {
/**
* Get the realm where the effective user comes from.
* The effective user is the es-security-runas-user if present or the authenticated user.
*
* Use {@code getEffectiveSubject().getRealm()} instead.
*/
@Deprecated
public RealmRef getSourceRealm() {
return lookedUpBy == null ? authenticatedBy : lookedUpBy;
}
Expand Down Expand Up @@ -231,40 +271,36 @@ public boolean isAuthenticatedWithServiceAccount() {

/**
* Whether the authenticating user is an API key, including a simple API key or a token created by an API key.
* @return
*/
public boolean isAuthenticatedAsApiKey() {
final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getAuthenticatedBy().getType());
assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getAuthenticatedBy().getName());
return result;
initializeSubjects();
return authenticatingSubject.getType() == Subject.Type.API_KEY;
}

public boolean isAuthenticatedAnonymously() {
// TODO: this is not entirely accurate if anonymous user can create a token
private boolean isAuthenticatedAnonymously() {
return AuthenticationType.ANONYMOUS.equals(getAuthenticationType());
}

public boolean isAuthenticatedInternally() {
private boolean isAuthenticatedInternally() {
return AuthenticationType.INTERNAL.equals(getAuthenticationType());
}

/**
* Authenticate with a service account and no run-as
*/
public boolean isServiceAccount() {
final boolean result = ServiceAccountSettings.REALM_TYPE.equals(getSourceRealm().getType());
assert false == result || ServiceAccountSettings.REALM_NAME.equals(getSourceRealm().getName())
: "service account realm name mismatch";
return result;
initializeSubjects();
return effectiveSubject.getType() == Subject.Type.SERVICE_ACCOUNT;
}

/**
* Whether the effective user is an API key, this including a simple API key authentication
* or a token created by the API key.
*/
public boolean isApiKey() {
final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getSourceRealm().getType());
assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getSourceRealm().getName()) : "api key realm name mismatch";
return result;
initializeSubjects();
return effectiveSubject.getType() == Subject.Type.API_KEY;
}

/**
Expand Down Expand Up @@ -321,10 +357,8 @@ public boolean canAccessResourcesOf(Authentication resourceCreatorAuthentication
).containsAll(EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType()))
: "cross AuthenticationType comparison for canAccessResourcesOf is not applicable for: "
+ EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType());
final AuthenticationContext myAuthContext = AuthenticationContext.fromAuthentication(this);
final AuthenticationContext creatorAuthContext = AuthenticationContext.fromAuthentication(resourceCreatorAuthentication);
final Subject mySubject = myAuthContext.getEffectiveSubject();
final Subject creatorSubject = creatorAuthContext.getEffectiveSubject();
final Subject mySubject = getEffectiveSubject();
final Subject creatorSubject = resourceCreatorAuthentication.getEffectiveSubject();
return mySubject.canAccessResourcesOf(creatorSubject);
}

Expand Down Expand Up @@ -410,6 +444,21 @@ public void toXContentFragment(XContentBuilder builder) throws IOException {
}
}

private void initializeSubjects() {
if (authenticatingSubject == null) {
if (user.isRunAs()) {
authenticatingSubject = new Subject(user.authenticatedUser(), authenticatedBy, version, metadata);
// The lookup user for run-as currently don't have authentication metadata associated with them because
// lookupUser only returns the User object. The lookup user for authorization delegation does have
// authentication metadata, but the realm does not expose this difference between authenticatingUser and
// delegateUser so effectively this is handled together with the authenticatingSubject not effectiveSubject.
effectiveSubject = new Subject(user, lookedUpBy, version, Map.of());
} else {
authenticatingSubject = effectiveSubject = new Subject(user, authenticatedBy, version, metadata);
}
}
}

private void assertApiKeyMetadata() {
assert (false == isAuthenticatedAsApiKey()) || (this.metadata.get(AuthenticationField.API_KEY_ID_KEY) != null)
: "API KEY authentication requires metadata to contain API KEY id, and the value must be non-null.";
Expand Down Expand Up @@ -757,6 +806,7 @@ static boolean equivalentRealms(String name1, String type1, String name2, String
}
}

// TODO: Rename to AuthenticationMethod
public enum AuthenticationType {
REALM,
API_KEY,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationContext;
import org.elasticsearch.xpack.core.security.authc.AuthenticationTests;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmDomain;
Expand Down Expand Up @@ -143,7 +142,7 @@ public void testGetProfileByAuthenticationUnderDomain() {
),
null
);
final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject();
final Subject subject = authentication.getEffectiveSubject();

// Profile does not exist yet
final PlainActionFuture<ProfileService.VersionedDocument> future1 = new PlainActionFuture<>();
Expand Down Expand Up @@ -189,7 +188,7 @@ public void testGetProfileByAuthenticationDomainless() {
new Authentication.RealmRef(realmIdentifier1.getName(), realmIdentifier1.getType(), nodeName),
null
);
final Subject subject1 = AuthenticationContext.fromAuthentication(authentication1).getEffectiveSubject();
final Subject subject1 = authentication1.getEffectiveSubject();

final PlainActionFuture<ProfileService.VersionedDocument> future1 = new PlainActionFuture<>();
profileService.searchVersionedDocumentForSubject(subject1, future1);
Expand All @@ -206,7 +205,7 @@ public void testGetProfileByAuthenticationDomainless() {
new Authentication.RealmRef(realmIdentifier2.getName(), realmIdentifier2.getType(), nodeName, realmDomain1),
null
);
final Subject subject2 = AuthenticationContext.fromAuthentication(authentication2).getEffectiveSubject();
final Subject subject2 = authentication2.getEffectiveSubject();

final PlainActionFuture<ProfileService.VersionedDocument> future2 = new PlainActionFuture<>();
profileService.searchVersionedDocumentForSubject(subject2, future2);
Expand All @@ -219,7 +218,7 @@ public void testGetProfileByAuthenticationDomainless() {
new Authentication.RealmRef(realmIdentifier2.getName(), realmIdentifier2.getType(), nodeName),
null
);
final Subject subject3 = AuthenticationContext.fromAuthentication(authentication3).getEffectiveSubject();
final Subject subject3 = authentication3.getEffectiveSubject();

final PlainActionFuture<ProfileService.VersionedDocument> future3 = new PlainActionFuture<>();
profileService.searchVersionedDocumentForSubject(subject3, future3);
Expand Down Expand Up @@ -406,7 +405,7 @@ public void testProfileDocumentPassCanAccessResourceCheck() {
authentication = authentication.token();
}
}
final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject();
final Subject subject = authentication.getEffectiveSubject();
final ProfileService profileService = getInstanceFromRandomNode(ProfileService.class);
final PlainActionFuture<Profile> future1 = new PlainActionFuture<>();
profileService.activateProfile(authentication, future1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationContext;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.support.DLSRoleQueryValidator;
Expand Down Expand Up @@ -54,7 +53,7 @@ public void generateApiKey(Authentication authentication, CreateApiKeyRequest re
apiKeyService.createApiKey(authentication, request, roleDescriptors, listener);
}, listener::onFailure);

final Subject effectiveSubject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject();
final Subject effectiveSubject = authentication.getEffectiveSubject();

// Retain current behaviour that User of an API key authentication has no roles
if (effectiveSubject.getType() == Subject.Type.API_KEY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.elasticsearch.core.Tuple;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationContext;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authz.RestrictedIndices;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
Expand Down Expand Up @@ -170,11 +169,10 @@ public void providersChanged() {
}

public void getRoles(Authentication authentication, ActionListener<Tuple<Role, Role>> roleActionListener) {
final AuthenticationContext authenticationContext = AuthenticationContext.fromAuthentication(authentication);
getRole(authenticationContext.getEffectiveSubject(), ActionListener.wrap(role -> {
if (authenticationContext.isRunAs()) {
getRole(authentication.getEffectiveSubject(), ActionListener.wrap(role -> {
if (authentication.isRunAs()) {
getRole(
authenticationContext.getAuthenticatingSubject(),
authentication.getAuthenticatingSubject(),
ActionListener.wrap(
authenticatingRole -> roleActionListener.onResponse(new Tuple<>(role, authenticatingRole)),
roleActionListener::onFailure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
import org.elasticsearch.xpack.core.security.action.profile.SearchProfilesResponse;
import org.elasticsearch.xpack.core.security.action.profile.UpdateProfileDataRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationContext;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
Expand Down Expand Up @@ -116,7 +115,7 @@ public void getProfile(String uid, @Nullable Set<String> dataKeys, ActionListene
* to submit the request.
*/
public void activateProfile(Authentication authentication, ActionListener<Profile> listener) {
final Subject subject = AuthenticationContext.fromAuthentication(authentication).getEffectiveSubject();
final Subject subject = authentication.getEffectiveSubject();
if (Subject.Type.USER != subject.getType()) {
listener.onFailure(
new IllegalArgumentException(
Expand Down
Loading

0 comments on commit 7e79110

Please sign in to comment.