mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
[auth] Decouple session & API token management from ManagedUser implementation & JaasAuthenticationProvider fixes (#5325)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
+14
-10
@@ -72,10 +72,10 @@ public class JaasAuthenticationProvider implements AuthenticationProvider {
|
||||
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Principal userPrincipal = new GenericUser(name);
|
||||
Subject subject = new Subject(true, Set.of(userPrincipal), Set.of(), Set.of(userCredentials));
|
||||
Subject subject = new Subject(DEFAULT_REALM.equals(realmName), Set.of(userPrincipal), Set.of(),
|
||||
Set.of(userCredentials));
|
||||
|
||||
Thread.currentThread().setContextClassLoader(ManagedUserLoginModule.class.getClassLoader());
|
||||
LoginContext loginContext = new LoginContext(realmName, subject, new CallbackHandler() {
|
||||
CallbackHandler callbackHandler = new CallbackHandler() {
|
||||
@Override
|
||||
public void handle(@NonNullByDefault({}) Callback[] callbacks)
|
||||
throws IOException, UnsupportedCallbackException {
|
||||
@@ -89,7 +89,16 @@ public class JaasAuthenticationProvider implements AuthenticationProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, new ManagedUserLoginConfiguration());
|
||||
};
|
||||
|
||||
LoginContext loginContext;
|
||||
if (DEFAULT_REALM.equals(realmName)) {
|
||||
Thread.currentThread().setContextClassLoader(ManagedUserLoginModule.class.getClassLoader());
|
||||
loginContext = new LoginContext(realmName, subject, callbackHandler,
|
||||
new ManagedUserLoginConfiguration());
|
||||
} else {
|
||||
loginContext = new LoginContext(realmName, subject, callbackHandler);
|
||||
}
|
||||
loginContext.login();
|
||||
|
||||
return getAuthentication(name, loginContext.getSubject());
|
||||
@@ -106,12 +115,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider {
|
||||
}
|
||||
|
||||
private String[] getRoles(Set<Principal> principals) {
|
||||
String[] roles = new String[principals.size()];
|
||||
int i = 0;
|
||||
for (Principal principal : principals) {
|
||||
roles[i++] = principal.getName();
|
||||
}
|
||||
return roles;
|
||||
return principals.stream().map(Principal::getName).distinct().toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Activate
|
||||
|
||||
+4
-4
@@ -27,9 +27,9 @@ import javax.ws.rs.core.HttpHeaders;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.core.auth.AuthenticatedUser;
|
||||
import org.openhab.core.auth.AuthenticationException;
|
||||
import org.openhab.core.auth.AuthenticationProvider;
|
||||
import org.openhab.core.auth.ManagedUser;
|
||||
import org.openhab.core.auth.PendingToken;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.auth.User;
|
||||
@@ -168,15 +168,15 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet {
|
||||
|
||||
String authorizationCode = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
if (user instanceof ManagedUser managedUser) {
|
||||
if (user instanceof AuthenticatedUser authenticatedUser) {
|
||||
String codeChallenge = params.containsKey("code_challenge") ? params.get("code_challenge")[0] : null;
|
||||
String codeChallengeMethod = params.containsKey("code_challenge_method")
|
||||
? params.get("code_challenge_method")[0]
|
||||
: null;
|
||||
PendingToken pendingToken = new PendingToken(authorizationCode, clientId, baseRedirectUri, scope,
|
||||
codeChallenge, codeChallengeMethod);
|
||||
managedUser.setPendingToken(pendingToken);
|
||||
userRegistry.update(managedUser);
|
||||
authenticatedUser.setPendingToken(pendingToken);
|
||||
userRegistry.update(authenticatedUser);
|
||||
}
|
||||
|
||||
String state = params.containsKey("state") ? params.get("state")[0] : null;
|
||||
|
||||
+5
-4
@@ -22,9 +22,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.auth.AuthenticatedUser;
|
||||
import org.openhab.core.auth.AuthenticationException;
|
||||
import org.openhab.core.auth.AuthenticationProvider;
|
||||
import org.openhab.core.auth.ManagedUser;
|
||||
import org.openhab.core.auth.User;
|
||||
import org.openhab.core.auth.UserRegistry;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
@@ -101,8 +101,9 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet {
|
||||
User user = login(username, password);
|
||||
String newApiToken;
|
||||
|
||||
if (user instanceof ManagedUser managedUser) {
|
||||
if (managedUser.getApiTokens().stream().anyMatch(apiToken -> apiToken.getName().equals(tokenName))) {
|
||||
if (user instanceof AuthenticatedUser authenticatedUser) {
|
||||
if (authenticatedUser.getApiTokens().stream()
|
||||
.anyMatch(apiToken -> apiToken.getName().equals(tokenName))) {
|
||||
resp.setContentType("text/html;charset=UTF-8");
|
||||
resp.getWriter().append(
|
||||
getPageBody(params, getLocalizedMessage("auth.createapitoken.name.unique.fail"), false));
|
||||
@@ -119,7 +120,7 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet {
|
||||
}
|
||||
newApiToken = userRegistry.addUserApiToken(user, tokenName, tokenScope);
|
||||
} else {
|
||||
throw new AuthenticationException("User is not managed");
|
||||
throw new AuthenticationException("User authentication is not managed by openHAB");
|
||||
}
|
||||
|
||||
String resultMessage = getLocalizedMessage("auth.createapitoken.success") + "<br /><br /><code>"
|
||||
|
||||
+80
-40
@@ -13,6 +13,7 @@
|
||||
package org.openhab.core.io.rest.auth.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
@@ -41,7 +42,7 @@ import javax.ws.rs.core.SecurityContext;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.jose4j.base64url.Base64Url;
|
||||
import org.openhab.core.auth.ManagedUser;
|
||||
import org.openhab.core.auth.AuthenticatedUser;
|
||||
import org.openhab.core.auth.PendingToken;
|
||||
import org.openhab.core.auth.User;
|
||||
import org.openhab.core.auth.UserApiToken;
|
||||
@@ -145,6 +146,7 @@ public class TokenResource implements RESTResource {
|
||||
@Path("/sessions")
|
||||
@Operation(operationId = "getSessionsForCurrentUser", summary = "List the sessions associated to the authenticated user.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = UserSessionDTO.class)))),
|
||||
@ApiResponse(responseCode = "400", description = "User authentication is not managed by openHAB"),
|
||||
@ApiResponse(responseCode = "401", description = "User is not authenticated"),
|
||||
@ApiResponse(responseCode = "404", description = "User not found") })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@@ -153,12 +155,16 @@ public class TokenResource implements RESTResource {
|
||||
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||
}
|
||||
|
||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
User user = userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
if (user == null) {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||
}
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
"User authentication is not managed by openHAB");
|
||||
}
|
||||
|
||||
Stream<UserSessionDTO> sessions = user.getSessions().stream().map(this::toUserSessionDTO);
|
||||
Stream<UserSessionDTO> sessions = authenticatedUser.getSessions().stream().map(this::toUserSessionDTO);
|
||||
return Response.ok(new Stream2JSONInputStream(sessions)).build();
|
||||
}
|
||||
|
||||
@@ -166,6 +172,7 @@ public class TokenResource implements RESTResource {
|
||||
@Path("/apitokens")
|
||||
@Operation(operationId = "getApiTokens", summary = "List the API tokens associated to the authenticated user.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = UserApiTokenDTO.class)))),
|
||||
@ApiResponse(responseCode = "400", description = "User authentication is not managed by openHAB"),
|
||||
@ApiResponse(responseCode = "401", description = "User is not authenticated"),
|
||||
@ApiResponse(responseCode = "404", description = "User not found") })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@@ -174,12 +181,16 @@ public class TokenResource implements RESTResource {
|
||||
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||
}
|
||||
|
||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
User user = userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
if (user == null) {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||
}
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
"User authentication is not managed by openHAB");
|
||||
}
|
||||
|
||||
Stream<UserApiTokenDTO> sessions = user.getApiTokens().stream().map(this::toUserApiTokenDTO);
|
||||
Stream<UserApiTokenDTO> sessions = authenticatedUser.getApiTokens().stream().map(this::toUserApiTokenDTO);
|
||||
return Response.ok(new Stream2JSONInputStream(sessions)).build();
|
||||
}
|
||||
|
||||
@@ -187,6 +198,7 @@ public class TokenResource implements RESTResource {
|
||||
@Path("/apitokens/{name}")
|
||||
@Operation(operationId = "removeApiToken", summary = "Revoke a specified API token associated to the authenticated user.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "400", description = "User authentication is not managed by openHAB"),
|
||||
@ApiResponse(responseCode = "401", description = "User is not authenticated"),
|
||||
@ApiResponse(responseCode = "404", description = "User or API token not found") })
|
||||
public Response removeApiToken(@Context SecurityContext securityContext, @PathParam("name") String name) {
|
||||
@@ -194,12 +206,16 @@ public class TokenResource implements RESTResource {
|
||||
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||
}
|
||||
|
||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
User user = userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
if (user == null) {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||
}
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
"User authentication is not managed by openHAB");
|
||||
}
|
||||
|
||||
Optional<UserApiToken> userApiToken = user.getApiTokens().stream()
|
||||
Optional<UserApiToken> userApiToken = authenticatedUser.getApiTokens().stream()
|
||||
.filter(apiToken -> apiToken.getName().equals(name)).findAny();
|
||||
if (userApiToken.isEmpty()) {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "No API token found with that name");
|
||||
@@ -214,6 +230,7 @@ public class TokenResource implements RESTResource {
|
||||
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
|
||||
@Operation(operationId = "deleteSession", summary = "Delete the session associated with a refresh token.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "400", description = "User authentication is not managed by openHAB"),
|
||||
@ApiResponse(responseCode = "401", description = "User is not authenticated"),
|
||||
@ApiResponse(responseCode = "404", description = "User or refresh token not found") })
|
||||
public Response deleteSession(@Nullable @FormParam("refresh_token") String refreshToken,
|
||||
@@ -223,16 +240,22 @@ public class TokenResource implements RESTResource {
|
||||
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||
}
|
||||
|
||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
User user = userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||
if (user == null) {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||
}
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
"User authentication is not managed by openHAB");
|
||||
}
|
||||
|
||||
Optional<UserSession> session;
|
||||
if (refreshToken != null) {
|
||||
session = user.getSessions().stream().filter(s -> s.getRefreshToken().equals(refreshToken)).findAny();
|
||||
session = authenticatedUser.getSessions().stream().filter(s -> s.getRefreshToken().equals(refreshToken))
|
||||
.findAny();
|
||||
} else if (id != null) {
|
||||
session = user.getSessions().stream().filter(s -> s.getSessionId().startsWith(id + "-")).findAny();
|
||||
session = authenticatedUser.getSessions().stream().filter(s -> s.getSessionId().startsWith(id + "-"))
|
||||
.findAny();
|
||||
} else {
|
||||
throw new IllegalArgumentException("no refresh_token or id specified");
|
||||
}
|
||||
@@ -249,7 +272,8 @@ public class TokenResource implements RESTResource {
|
||||
// jakarta.ws.rs/jakarta.ws.rs-api/3.1.0 or newer
|
||||
response.header("Set-Cookie",
|
||||
SESSIONID_COOKIE_FORMAT.formatted(UUID.randomUUID(), domainUri.getHost()));
|
||||
} catch (Exception e) {
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("Unexpected error deleting session", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,20 +296,26 @@ public class TokenResource implements RESTResource {
|
||||
private Response processAuthorizationCodeGrant(String code, String redirectUri, String clientId,
|
||||
@Nullable String codeVerifier, boolean useCookie) throws TokenEndpointException, NoSuchAlgorithmException {
|
||||
// find a user with the authorization code pending
|
||||
Optional<User> user = userRegistry.getAll().stream().filter(u -> {
|
||||
ManagedUser managedUser = (ManagedUser) u;
|
||||
Optional<User> optionalUser = userRegistry.getAll().stream().filter(u -> {
|
||||
if (!(u instanceof AuthenticatedUser authenticatedUser)) {
|
||||
return false;
|
||||
}
|
||||
@Nullable
|
||||
PendingToken pendingToken = managedUser.getPendingToken();
|
||||
PendingToken pendingToken = authenticatedUser.getPendingToken();
|
||||
return (pendingToken != null && pendingToken.getAuthorizationCode().equals(code));
|
||||
}).findAny();
|
||||
|
||||
if (user.isEmpty()) {
|
||||
if (optionalUser.isEmpty()) {
|
||||
logger.warn("Couldn't find a user with the provided authentication code pending");
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user.get();
|
||||
PendingToken pendingToken = managedUser.getPendingToken();
|
||||
User user = optionalUser.get();
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
logger.warn("User '{}' authentication is not managed by openHAB", user.getName());
|
||||
throw new TokenEndpointException(ErrorType.INVALID_CLIENT);
|
||||
}
|
||||
PendingToken pendingToken = authenticatedUser.getPendingToken();
|
||||
if (pendingToken == null) {
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
@@ -335,12 +365,12 @@ public class TokenResource implements RESTResource {
|
||||
}
|
||||
|
||||
// create an access token
|
||||
String accessToken = jwtHelper.getJwtAccessToken(managedUser, clientId, scope, TOKEN_LIFETIME);
|
||||
String accessToken = jwtHelper.getJwtAccessToken(authenticatedUser, clientId, scope, TOKEN_LIFETIME);
|
||||
|
||||
UserSession newSession = new UserSession(sessionId, newRefreshToken, clientId, redirectUri, scope);
|
||||
|
||||
ResponseBuilder response = Response.ok(
|
||||
new TokenResponseDTO(accessToken, "bearer", TOKEN_LIFETIME * 60, newRefreshToken, scope, managedUser));
|
||||
ResponseBuilder response = Response.ok(new TokenResponseDTO(accessToken, "bearer", TOKEN_LIFETIME * 60,
|
||||
newRefreshToken, scope, authenticatedUser));
|
||||
|
||||
// if the client has requested an http-only cookie for the session, set it
|
||||
if (useCookie) {
|
||||
@@ -366,9 +396,9 @@ public class TokenResource implements RESTResource {
|
||||
}
|
||||
|
||||
// add the new session to the user profile and clear the pending token information
|
||||
managedUser.getSessions().add(newSession);
|
||||
managedUser.setPendingToken(null);
|
||||
userRegistry.update(managedUser);
|
||||
authenticatedUser.getSessions().add(newSession);
|
||||
authenticatedUser.setPendingToken(null);
|
||||
userRegistry.update(authenticatedUser);
|
||||
|
||||
return response.build();
|
||||
}
|
||||
@@ -380,42 +410,52 @@ public class TokenResource implements RESTResource {
|
||||
}
|
||||
|
||||
// find a user associated with the provided refresh token
|
||||
Optional<User> refreshTokenUser = userRegistry.getAll().stream().filter(
|
||||
u -> ((ManagedUser) u).getSessions().stream().anyMatch(s -> refreshToken.equals(s.getRefreshToken())))
|
||||
Optional<User> optionalRefreshTokenUser = userRegistry.getAll().stream()
|
||||
.filter(u -> u instanceof AuthenticatedUser).filter(u -> ((AuthenticatedUser) u).getSessions().stream()
|
||||
.anyMatch(s -> refreshToken.equals(s.getRefreshToken())))
|
||||
.findAny();
|
||||
|
||||
if (refreshTokenUser.isEmpty()) {
|
||||
if (optionalRefreshTokenUser.isEmpty()) {
|
||||
logger.warn("Couldn't find a user with a session matching the provided refresh_token");
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
|
||||
// get the session from the refresh token
|
||||
ManagedUser refreshTokenManagedUser = (ManagedUser) refreshTokenUser.get();
|
||||
UserSession session = refreshTokenManagedUser.getSessions().stream()
|
||||
.filter(s -> s.getRefreshToken().equals(refreshToken)).findAny().get();
|
||||
User refreshTokenUser = optionalRefreshTokenUser.get();
|
||||
if (!(refreshTokenUser instanceof AuthenticatedUser refreshTokenAuthenticatedUser)) {
|
||||
logger.warn("User '{}' authentication is not managed by openHAB", refreshTokenUser.getName());
|
||||
throw new TokenEndpointException(ErrorType.INVALID_CLIENT);
|
||||
}
|
||||
Optional<UserSession> optSession = refreshTokenAuthenticatedUser.getSessions().stream()
|
||||
.filter(s -> s.getRefreshToken().equals(refreshToken)).findAny();
|
||||
if (optSession.isEmpty()) {
|
||||
logger.warn("Not refreshing token for user {}, missing session", refreshTokenAuthenticatedUser.getName());
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
UserSession session = optSession.get();
|
||||
|
||||
// if the cookie flag is present on the session, verify that the cookie is present and corresponds
|
||||
// to this session
|
||||
if (session.hasSessionCookie()) {
|
||||
if (sessionCookie == null || !sessionCookie.getValue().equals(session.getSessionId())) {
|
||||
logger.warn("Not refreshing token for session {} of user {}, missing or invalid session cookie",
|
||||
session.getSessionId(), refreshTokenManagedUser.getName());
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
if (session.hasSessionCookie()
|
||||
&& (sessionCookie == null || !sessionCookie.getValue().equals(session.getSessionId()))) {
|
||||
logger.warn("Not refreshing token for session {} of user {}, missing or invalid session cookie",
|
||||
session.getSessionId(), refreshTokenAuthenticatedUser.getName());
|
||||
throw new TokenEndpointException(ErrorType.INVALID_GRANT);
|
||||
}
|
||||
|
||||
// issue a new access token
|
||||
String refreshedAccessToken = jwtHelper.getJwtAccessToken(refreshTokenManagedUser, clientId, session.getScope(),
|
||||
TOKEN_LIFETIME);
|
||||
String refreshedAccessToken = jwtHelper.getJwtAccessToken(refreshTokenAuthenticatedUser, clientId,
|
||||
session.getScope(), TOKEN_LIFETIME);
|
||||
|
||||
logger.debug("Refreshing session {} of user {}", session.getSessionId(), refreshTokenManagedUser.getName());
|
||||
logger.debug("Refreshing session {} of user {}", session.getSessionId(),
|
||||
refreshTokenAuthenticatedUser.getName());
|
||||
|
||||
ResponseBuilder refreshResponse = Response.ok(new TokenResponseDTO(refreshedAccessToken, "bearer",
|
||||
TOKEN_LIFETIME * 60, refreshToken, session.getScope(), refreshTokenManagedUser));
|
||||
TOKEN_LIFETIME * 60, refreshToken, session.getScope(), refreshTokenAuthenticatedUser));
|
||||
|
||||
// update the last refresh time of the session in the user's profile
|
||||
session.setLastRefreshTime(new Date());
|
||||
userRegistry.update(refreshTokenManagedUser);
|
||||
userRegistry.update(refreshTokenAuthenticatedUser);
|
||||
|
||||
return refreshResponse.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.auth;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link User} that is authenticated by the system itself, i.e., session state and API tokens are managed by the
|
||||
* system.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AuthenticatedUser extends User {
|
||||
/**
|
||||
* Gets the pending token information for this user, if any.
|
||||
*
|
||||
* @return the pending token information or null if there is none
|
||||
*/
|
||||
@Nullable
|
||||
PendingToken getPendingToken();
|
||||
|
||||
/**
|
||||
* Sets or clears the pending token information for this user.
|
||||
*
|
||||
* @param pendingToken the pending token information or null to clear it
|
||||
*/
|
||||
void setPendingToken(@Nullable PendingToken pendingToken);
|
||||
|
||||
/**
|
||||
* Gets the current persistent sessions for this user.
|
||||
*
|
||||
* @return the list of sessions
|
||||
*/
|
||||
List<UserSession> getSessions();
|
||||
|
||||
/**
|
||||
* Replaces the list of sessions by a new one.
|
||||
*
|
||||
* @param sessions the new list of sessions
|
||||
*/
|
||||
void setSessions(List<UserSession> sessions);
|
||||
|
||||
/**
|
||||
* Gets the long-term API tokens for this user
|
||||
*
|
||||
* @return the API tokens
|
||||
*/
|
||||
List<UserApiToken> getApiTokens();
|
||||
|
||||
/**
|
||||
* Replaces the list of API tokens by a new one.
|
||||
*
|
||||
* @param apiTokens the new API tokens
|
||||
*/
|
||||
void setApiTokens(List<UserApiToken> apiTokens);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
* @author Yannick Schaus - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ManagedUser implements User {
|
||||
public class ManagedUser implements AuthenticatedUser {
|
||||
|
||||
private String name;
|
||||
private String passwordHash;
|
||||
@@ -118,56 +118,32 @@ public class ManagedUser implements User {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pending token information for this user, if any.
|
||||
*
|
||||
* @return the pending token information or null if there is none
|
||||
*/
|
||||
@Override
|
||||
public @Nullable PendingToken getPendingToken() {
|
||||
return pendingToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or clears the pending token information for this user.
|
||||
*
|
||||
* @param pendingToken the pending token information or null to clear it
|
||||
*/
|
||||
@Override
|
||||
public void setPendingToken(@Nullable PendingToken pendingToken) {
|
||||
this.pendingToken = pendingToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current persistent sessions for this user.
|
||||
*
|
||||
* @return the list of sessions
|
||||
*/
|
||||
@Override
|
||||
public List<UserSession> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the list of sessions by a new one.
|
||||
*
|
||||
* @param sessions the new list of sessions
|
||||
*/
|
||||
@Override
|
||||
public void setSessions(List<UserSession> sessions) {
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the long-term API tokens for this user
|
||||
*
|
||||
* @return the API tokens
|
||||
*/
|
||||
@Override
|
||||
public List<UserApiToken> getApiTokens() {
|
||||
return apiTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the list of API tokens by a new one.
|
||||
*
|
||||
* @param apiTokens the new API tokens
|
||||
*/
|
||||
@Override
|
||||
public void setApiTokens(List<UserApiToken> apiTokens) {
|
||||
this.apiTokens = apiTokens;
|
||||
}
|
||||
|
||||
+29
-32
@@ -26,6 +26,7 @@ import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.auth.AuthenticatedUser;
|
||||
import org.openhab.core.auth.Authentication;
|
||||
import org.openhab.core.auth.AuthenticationException;
|
||||
import org.openhab.core.auth.Credentials;
|
||||
@@ -153,19 +154,20 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
|
||||
throw new AuthenticationException("Invalid API token format");
|
||||
}
|
||||
for (User user : getAll()) {
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
for (UserApiToken userApiToken : managedUser.getApiTokens()) {
|
||||
// only check if the name in the token matches
|
||||
if (!userApiToken.getName().equals(apiTokenParts[1])) {
|
||||
continue;
|
||||
}
|
||||
String[] existingTokenHashAndSalt = userApiToken.getApiToken().split(":");
|
||||
String incomingTokenHash = hash(apiTokenCreds.getApiToken(), existingTokenHashAndSalt[1],
|
||||
APITOKEN_ITERATIONS).get();
|
||||
if (user instanceof AuthenticatedUser authenticatedUser) {
|
||||
for (UserApiToken userApiToken : authenticatedUser.getApiTokens()) {
|
||||
// only check if the name in the token matches
|
||||
if (!userApiToken.getName().equals(apiTokenParts[1])) {
|
||||
continue;
|
||||
}
|
||||
String[] existingTokenHashAndSalt = userApiToken.getApiToken().split(":");
|
||||
String incomingTokenHash = hash(apiTokenCreds.getApiToken(), existingTokenHashAndSalt[1],
|
||||
APITOKEN_ITERATIONS).get();
|
||||
|
||||
if (incomingTokenHash.equals(existingTokenHashAndSalt[0])) {
|
||||
return new Authentication(managedUser.getName(),
|
||||
managedUser.getRoles().stream().toArray(String[]::new), userApiToken.getScope());
|
||||
if (incomingTokenHash.equals(existingTokenHashAndSalt[0])) {
|
||||
return new Authentication(authenticatedUser.getName(),
|
||||
authenticatedUser.getRoles().toArray(String[]::new), userApiToken.getScope());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,47 +194,43 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
|
||||
|
||||
@Override
|
||||
public void addUserSession(User user, UserSession session) {
|
||||
if (!(user instanceof ManagedUser)) {
|
||||
throw new IllegalArgumentException("User is not managed: " + user.getName());
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
throw new IllegalArgumentException("User authentication is not managed by openHAB: " + user.getName());
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
managedUser.getSessions().add(session);
|
||||
authenticatedUser.getSessions().add(session);
|
||||
update(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSession(User user, UserSession session) {
|
||||
if (!(user instanceof ManagedUser)) {
|
||||
throw new IllegalArgumentException("User is not managed: " + user.getName());
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
throw new IllegalArgumentException("User authentication is not managed by openHAB: " + user.getName());
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
managedUser.getSessions().remove(session);
|
||||
authenticatedUser.getSessions().remove(session);
|
||||
update(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSessions(User user) {
|
||||
if (!(user instanceof ManagedUser)) {
|
||||
throw new IllegalArgumentException("User is not managed: " + user.getName());
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
throw new IllegalArgumentException("User authentication is not managed by openHAB: " + user.getName());
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
managedUser.getSessions().clear();
|
||||
authenticatedUser.getSessions().clear();
|
||||
update(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addUserApiToken(User user, String name, String scope) {
|
||||
if (!(user instanceof ManagedUser)) {
|
||||
throw new IllegalArgumentException("User is not managed: " + user.getName());
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
throw new IllegalArgumentException("User authentication is not managed by openHAB: " + user.getName());
|
||||
}
|
||||
if (!name.matches("[a-zA-Z0-9]*")) {
|
||||
throw new IllegalArgumentException("API token name format invalid, alphanumeric characters only");
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
String tokenSalt = generateSalt(KEY_LENGTH / 8).get();
|
||||
byte[] rnd = new byte[64];
|
||||
RAND.nextBytes(rnd);
|
||||
@@ -242,7 +240,7 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
|
||||
|
||||
UserApiToken userApiToken = new UserApiToken(name, tokenHash + ":" + tokenSalt, scope);
|
||||
|
||||
managedUser.getApiTokens().add(userApiToken);
|
||||
authenticatedUser.getApiTokens().add(userApiToken);
|
||||
update(user);
|
||||
|
||||
return token;
|
||||
@@ -250,12 +248,11 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
|
||||
|
||||
@Override
|
||||
public void removeUserApiToken(User user, UserApiToken userApiToken) {
|
||||
if (!(user instanceof ManagedUser)) {
|
||||
throw new IllegalArgumentException("User is not managed: " + user.getName());
|
||||
if (!(user instanceof AuthenticatedUser authenticatedUser)) {
|
||||
throw new IllegalArgumentException("User authentication is not managed by openHAB: " + user.getName());
|
||||
}
|
||||
|
||||
ManagedUser managedUser = (ManagedUser) user;
|
||||
managedUser.getApiTokens().remove(userApiToken);
|
||||
authenticatedUser.getApiTokens().remove(userApiToken);
|
||||
update(user);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user