Skip to content

Commit

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

It becomes more apparent that the most useful refactoring in #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 #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 authored Apr 26, 2022
1 parent 5bab086 commit 7a3dd5a
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 275 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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 final Subject authenticatingSubject;
private final Subject effectiveSubject;

public Authentication(User user, RealmRef authenticatedBy, RealmRef lookedUpBy) {
this(user, authenticatedBy, lookedUpBy, Version.CURRENT, AuthenticationType.REALM, Collections.emptyMap());
}
Expand All @@ -91,6 +94,16 @@ public Authentication(
this.version = version;
this.type = type;
this.metadata = metadata;
if (user.isRunAs()) {
authenticatingSubject = new Subject(user.authenticatedUser(), authenticatedBy, version, metadata);
// The lookup user for run-as currently doesn'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);
}
this.assertApiKeyMetadata();
this.assertDomainAssignment();
}
Expand All @@ -106,26 +119,75 @@ public Authentication(StreamInput in) throws IOException {
this.version = in.getVersion();
type = AuthenticationType.values()[in.readVInt()];
metadata = in.readMap();
if (user.isRunAs()) {
authenticatingSubject = new Subject(user.authenticatedUser(), authenticatedBy, version, metadata);
// The lookup user for run-as currently doesn'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);
}
this.assertApiKeyMetadata();
this.assertDomainAssignment();
}

/**
* Get the {@link Subject} that performs the actual authentication. This normally means it provides a credentials.
*/
public Subject getAuthenticatingSubject() {
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() {
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() {
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;
}

/**
* The use case for this method is largely trying to tell whether there is a run-as user
* and can be replaced by {@code isRunAs}
*/
@Deprecated
public RealmRef getLookedUpBy() {
return lookedUpBy;
}

/**
* 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 @@ -284,40 +346,33 @@ 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;
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;
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;
return effectiveSubject.getType() == Subject.Type.API_KEY;
}

/**
Expand Down Expand Up @@ -374,10 +429,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 @@ -810,6 +863,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 @@ -493,6 +493,10 @@ public static RealmRef randomRealmRef(boolean underDomain, boolean includeIntern
* The authentication can have any version from 7.0.0 to current and random metadata.
*/
public static Authentication randomAuthentication(User user, RealmRef realmRef) {
return randomAuthentication(user, realmRef, randomBoolean());
}

public static Authentication randomAuthentication(User user, RealmRef realmRef, boolean isRunAs) {
if (user == null) {
user = randomUser();
}
Expand All @@ -508,7 +512,7 @@ public static Authentication randomAuthentication(User user, RealmRef realmRef)
.distinct()
.collect(Collectors.toMap(s -> s, s -> randomAlphaOfLengthBetween(3, 8)));
}
return AuthenticationTestHelper.builder().user(user).realmRef(realmRef).version(version).metadata(metadata).build();
return AuthenticationTestHelper.builder().user(user).realmRef(realmRef).version(version).metadata(metadata).build(isRunAs);
}

public static Authentication randomApiKeyAuthentication(User user, String apiKeyId) {
Expand Down
Loading

0 comments on commit 7a3dd5a

Please sign in to comment.