in api/applib/src/main/java/org/apache/causeway/applib/services/user/UserMemento.java [67:399]
public record UserMemento(
/**
* The user's login name.
*/
@Property
@PropertyLayout(fieldSetId = "identity", sequence = "1", describedAs = "user's login name")
@NonNull String name,
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "details", sequence = "1")
@Nullable String realName,
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "details", sequence = "2")
@Nullable URL avatarUrl,
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "regional", sequence = "1")
@Nullable Locale languageLocale,
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "regional", sequence = "2")
@Nullable Locale numberFormatLocale,
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "regional", sequence = "3")
@Nullable Locale timeFormatLocale,
/**
* To support external security mechanisms such as keycloak,
* where the validity of the session is defined by headers in the request.
*/
@Property
@PropertyLayout(fieldSetId = "security", sequence = "1")
@NonNull AuthenticationSource authenticationSource,
@Property
@PropertyLayout(fieldSetId = "security", sequence = "3", named = "impersonating")
boolean isImpersonating,
/**
* Indicates which tenancy (or tenancies) this user has access to.
* <p>
* The interpretation of this token is implementation-specific.
*/
@Property(optionality = Optionality.OPTIONAL)
@PropertyLayout(fieldSetId = "security", sequence = "2",
describedAs = "tenancy (or tenancies) this user has access to")
@Nullable String multiTenancyToken,
/**
* A unique code given to this user during authentication.
* <p>
* This can be used to confirm that the user has been authenticated.
* It should return an empty string {@literal ""}
* if this is an anonymous (unauthenticated) user.
*/
@Property
@PropertyLayout(hidden = Where.EVERYWHERE)
@NonNull String authenticationCode,
/**
* The roles associated with this user.
*/
@Collection
@CollectionLayout(sequence = "1", describedAs = "roles associated with this user")
@NonNull Can<RoleMemento> roles
) implements Serializable {
public enum AuthenticationSource {
DEFAULT,
/**
* Instructs the <code>AuthenticationManager</code>
* to <i>not</i> cache this session in its internal map of sessions by validation code,
* and therefore to ignore this aspect when considering if an {@link InteractionContext} is valid or not.
*/
EXTERNAL;
public boolean isExternal() { return this == EXTERNAL; }
}
public static class TitleUiEvent extends CausewayModuleApplib.TitleUiEvent<UserMemento> {}
public static final String LOGICAL_TYPE_NAME = CausewayModuleApplib.NAMESPACE + ".UserMemento";
/** Also used by the wicket-viewer and its AuthorizeInstantiation(...) annotations;
* the actual value is arbitrary; however, we use namespace style to clarify the origin*/
public static final String AUTHORIZED_USER_ROLE = "org.apache.causeway.security.AUTHORIZED_USER_ROLE";
private static final UserMemento SYSTEM_USER = UserMemento.ofName("__system");
private static final String DEFAULT_AUTH_VALID_CODE = "";
// -- FACTORIES
/**
* The framework's internal user with unrestricted privileges.
*/
public static UserMemento system() { return SYSTEM_USER; }
/**
* Creates a new user with the specified name and no roles.
*/
public static UserMemento ofName(final @NonNull String name) {
return builder(name)
.roles(Can.empty())
.build();
}
/**
* Creates a new user with the specified name and assigned roles.
*/
public static UserMemento ofNameAndRoles(
final @NonNull String name,
final RoleMemento... roles) {
return builder(name)
.roles(Can.ofArray(roles))
.build();
}
/**
* Creates a new user with the specified name and assigned role names.
*/
public static UserMemento ofNameAndRoleNames(
final @NonNull String name,
final String... roleNames) {
return ofNameAndRoleNames(name, Stream.of(roleNames));
}
/**
* Creates a new user with the specified name and assigned role names.
*/
public static UserMemento ofNameAndRoleNames(
final @NonNull String name,
final @NonNull List<String> roleNames) {
return ofNameAndRoleNames(name, roleNames.stream());
}
/**
* Creates a new user with the specified name and assigned role names.
*/
public static UserMemento ofNameAndRoleNames(
final @NonNull String name,
final @NonNull Stream<String> roleNames) {
return builder(name)
.roles(roleNames
.map(RoleMemento::new)
.collect(Can.toCan()))
.build();
}
// -- UI TITLE
public static class UiSubscriber {
@Order(PriorityPrecedence.LATE)
@EventListener(UserMemento.TitleUiEvent.class)
public void on(final UserMemento.TitleUiEvent ev) {
var userMemento = Objects.requireNonNull(ev.getSource());
ev.setTitle(userMemento.nameFormatted());
}
}
// -- PROPERTIES
@Programmatic
public String nameFormatted() {
return isImpersonating()
? String.format("%s 👻", name)
: name;
}
@Programmatic
public UserMemento withRoleAdded(final String role) {
return asBuilder()
.roles(roles.add(new RoleMemento(role)))
.build();
}
/**
* Determine if the specified name is this user.
*
* <p>
*
* @return true if the names match (is case sensitive).
*/
@Programmatic
public boolean isCurrentUser(final @Nullable String userName) {
return name.equals(userName);
}
@Programmatic
public Stream<String> streamRoleNames() {
return roles.stream()
.map(RoleMemento::name);
}
@Programmatic
public boolean hasRoleName(final @Nullable String roleName) {
return streamRoleNames().anyMatch(myRoleName->myRoleName.equals(roleName));
}
/**
* Whether this {@link UserMemento}'s {@link UserMemento#roles() roles} contains the {@link SudoService}'s
* {@link SudoService#ACCESS_ALL_ROLE ACCESS_ALL_ROLE} role (meaning that security checks are disabled).
*/
@Programmatic
public boolean hasSudoAccessAllRole() {
return roles.contains(SudoService.ACCESS_ALL_ROLE);
}
// -- UTILITY
@Programmatic
public UserMementoBuilder asBuilder() {
//XXX update whenever new fields are added
return UserMemento.builderInternal()
.name(name)
.authenticationCode(authenticationCode)
.authenticationSource(authenticationSource)
.avatarUrl(avatarUrl)
.languageLocale(languageLocale)
.numberFormatLocale(numberFormatLocale)
.timeFormatLocale(timeFormatLocale)
.isImpersonating(isImpersonating)
.realName(realName)
.multiTenancyToken(multiTenancyToken)
.roles(roles);
}
@Programmatic
public UserLocale asUserLocale() {
var main = languageLocale!=null
? languageLocale
: Locale.getDefault();
return UserLocale.builder()
.languageLocale(main)
.numberFormatLocale(numberFormatLocale!=null
? numberFormatLocale
: main)
.timeFormatLocale(timeFormatLocale!=null
? timeFormatLocale
: main)
.build();
}
public static UserMementoBuilder builder(final String name) {
if (_Strings.isEmpty(name)) {
throw new IllegalArgumentException("Name not specified");
}
return UserMemento.builderInternal()
.name(name)
.authenticationSource(AuthenticationSource.DEFAULT)
.authenticationCode(DEFAULT_AUTH_VALID_CODE)
.roles(Can.empty());
}
// -- WITHERS
@Programmatic public UserMemento withRealName(String realName) {
return asBuilder().realName(realName).build();
}
@Programmatic public UserMemento withAvatarUrl(URL avatarUrl) {
return asBuilder().avatarUrl(avatarUrl).build();
}
@Programmatic public UserMemento withImpersonating(boolean impersonating) {
return asBuilder().isImpersonating(impersonating).build();
}
@Programmatic public UserMemento withAuthenticationCode(String authenticationCode) {
return asBuilder().authenticationCode(authenticationCode).build();
}
@Programmatic public UserMemento withAuthenticationSource(AuthenticationSource authenticationSource) {
return asBuilder().authenticationSource(authenticationSource).build();
}
@Programmatic public UserMemento withMultiTenancyToken(String multiTenancyToken) {
return asBuilder().multiTenancyToken(multiTenancyToken).build();
}
@Programmatic public UserMemento withLanguageLocale(Locale languageLocale) {
return asBuilder().languageLocale(languageLocale).build();
}
@Programmatic public UserMemento withNumberFormatLocale(Locale numberFormatLocale) {
return asBuilder().numberFormatLocale(numberFormatLocale).build();
}
@Programmatic public UserMemento withTimeFormatLocale(Locale timeFormatLocale) {
return asBuilder().timeFormatLocale(timeFormatLocale).build();
}
// -- OBJECT CONTRACT
@Override
public final boolean equals(Object obj) {
return (obj instanceof UserMemento other)
? isImpersonating == other.isImpersonating
&& Objects.equals(name, other.name)
&& Objects.equals(authenticationSource, other.authenticationSource)
&& Objects.equals(multiTenancyToken, other.multiTenancyToken)
&& Objects.equals(authenticationCode, other.authenticationCode)
&& Objects.equals(roles, other.roles)
: false;
}
@Override
public final int hashCode() {
return Objects.hash(isImpersonating, name, authenticationSource, multiTenancyToken, authenticationCode, roles);
}
// -- HELPER
private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
}
// -- DEPRECATIONS
/** @deprecated use {@link #name()} instead */
@Programmatic @Deprecated public String getName() { return name(); }
/** @deprecated use {@link #authenticationCode()} instead */
@Programmatic @Deprecated public String getAuthenticationCode() { return authenticationCode(); }
/** @deprecated use {@link #authenticationSource()} instead */
@Programmatic @Deprecated public AuthenticationSource getAuthenticationSource() { return authenticationSource(); }
/** @deprecated use {@link #avatarUrl()} instead */
@Programmatic @Deprecated public URL getAvatarUrl() { return avatarUrl(); }
/** @deprecated use {@link #realName()} instead */
@Programmatic @Deprecated public String getRealName() { return realName(); }
/** @deprecated use {@link #languageLocale()} instead */
@Programmatic @Deprecated public Locale getLanguageLocale() { return languageLocale(); }
/** @deprecated use {@link #numberFormatLocale()} instead */
@Programmatic @Deprecated public Locale getnumberFormatLocale() { return numberFormatLocale(); }
/** @deprecated use {@link #timeFormatLocale()} instead */
@Programmatic @Deprecated public Locale getTimeFormatLocale() { return timeFormatLocale(); }
/** @deprecated use {@link #multiTenancyToken()} instead */
@Programmatic @Deprecated public String getMultiTenancyToken() { return multiTenancyToken(); }
/** @deprecated use {@link #roles()} instead */
@Programmatic @Deprecated public List<RoleMemento> getRoles() { return roles().toList(); }
}