mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-26 20:21:33 +01:00
REST API: Re-enable auth (#1482)
* Add JAX-RS annotations to auth-related filter & dynamic feature. * Remove unnecessary Activator. * Remove BND file. * Build error responses explicity instead of throwing exceptions in TokenResource to avoid logging. Fixes #1477 Signed-off-by: Yannick Schaus <github@schaus.net>
This commit is contained in:
parent
5e8d7554d8
commit
bd976cf937
@ -1,2 +0,0 @@
|
|||||||
Bundle-SymbolicName: ${project.artifactId}
|
|
||||||
Bundle-Activator: org.openhab.core.io.rest.auth.internal.Activator
|
|
@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2020 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.io.rest.auth.internal;
|
|
||||||
|
|
||||||
import org.osgi.framework.BundleActivator;
|
|
||||||
import org.osgi.framework.BundleContext;
|
|
||||||
import org.osgi.framework.ServiceRegistration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers dynamic features contained in this bundle.
|
|
||||||
*
|
|
||||||
* @author Yannick Schaus - initial contribution
|
|
||||||
*/
|
|
||||||
public class Activator implements BundleActivator {
|
|
||||||
private static Activator instance;
|
|
||||||
|
|
||||||
private ServiceRegistration<?> rolesAllowedDynamicFeatureRegistration;
|
|
||||||
|
|
||||||
public static Activator getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(BundleContext context) throws Exception {
|
|
||||||
rolesAllowedDynamicFeatureRegistration = context.registerService(RolesAllowedDynamicFeatureImpl.class.getName(),
|
|
||||||
new RolesAllowedDynamicFeatureImpl(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop(BundleContext context) throws Exception {
|
|
||||||
instance = null;
|
|
||||||
if (rolesAllowedDynamicFeatureRegistration != null) {
|
|
||||||
rolesAllowedDynamicFeatureRegistration.unregister();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,18 +16,23 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import javax.annotation.Priority;
|
import javax.annotation.Priority;
|
||||||
import javax.security.sasl.AuthenticationException;
|
import javax.security.sasl.AuthenticationException;
|
||||||
import javax.ws.rs.NotAuthorizedException;
|
|
||||||
import javax.ws.rs.Priorities;
|
import javax.ws.rs.Priorities;
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
import javax.ws.rs.container.ContainerRequestFilter;
|
import javax.ws.rs.container.ContainerRequestFilter;
|
||||||
import javax.ws.rs.container.PreMatching;
|
import javax.ws.rs.container.PreMatching;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.SecurityContext;
|
import javax.ws.rs.core.SecurityContext;
|
||||||
import javax.ws.rs.ext.Provider;
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
import org.openhab.core.auth.Authentication;
|
import org.openhab.core.auth.Authentication;
|
||||||
|
import org.openhab.core.io.rest.JSONResponse;
|
||||||
|
import org.openhab.core.io.rest.RESTConstants;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This filter is responsible for parsing a token provided with a request, and hydrating a {@link SecurityContext} from
|
* This filter is responsible for parsing a token provided with a request, and hydrating a {@link SecurityContext} from
|
||||||
@ -36,9 +41,11 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
* @author Yannick Schaus - initial contribution
|
* @author Yannick Schaus - initial contribution
|
||||||
*/
|
*/
|
||||||
@PreMatching
|
@PreMatching
|
||||||
|
@Component
|
||||||
|
@JaxrsExtension
|
||||||
|
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||||
@Priority(Priorities.AUTHENTICATION)
|
@Priority(Priorities.AUTHENTICATION)
|
||||||
@Provider
|
@Provider
|
||||||
@Component(immediate = true, service = AuthFilter.class)
|
|
||||||
public class AuthFilter implements ContainerRequestFilter {
|
public class AuthFilter implements ContainerRequestFilter {
|
||||||
private static final String COOKIE_AUTH_HEADER = "X-OPENHAB-AUTH-HEADER";
|
private static final String COOKIE_AUTH_HEADER = "X-OPENHAB-AUTH-HEADER";
|
||||||
private static final String ALT_AUTH_HEADER = "X-OPENHAB-TOKEN";
|
private static final String ALT_AUTH_HEADER = "X-OPENHAB-TOKEN";
|
||||||
@ -81,7 +88,7 @@ public class AuthFilter implements ContainerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
throw new NotAuthorizedException("Invalid token");
|
requestContext.abortWith(JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "Invalid token"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,22 @@ import javax.annotation.Priority;
|
|||||||
import javax.annotation.security.DenyAll;
|
import javax.annotation.security.DenyAll;
|
||||||
import javax.annotation.security.PermitAll;
|
import javax.annotation.security.PermitAll;
|
||||||
import javax.annotation.security.RolesAllowed;
|
import javax.annotation.security.RolesAllowed;
|
||||||
import javax.ws.rs.ForbiddenException;
|
|
||||||
import javax.ws.rs.NotAuthorizedException;
|
|
||||||
import javax.ws.rs.Priorities;
|
import javax.ws.rs.Priorities;
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
import javax.ws.rs.container.ContainerRequestFilter;
|
import javax.ws.rs.container.ContainerRequestFilter;
|
||||||
import javax.ws.rs.container.DynamicFeature;
|
import javax.ws.rs.container.DynamicFeature;
|
||||||
import javax.ws.rs.container.ResourceInfo;
|
import javax.ws.rs.container.ResourceInfo;
|
||||||
import javax.ws.rs.core.FeatureContext;
|
import javax.ws.rs.core.FeatureContext;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.ext.Provider;
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
import org.openhab.core.auth.Role;
|
import org.openhab.core.auth.Role;
|
||||||
|
import org.openhab.core.io.rest.JSONResponse;
|
||||||
|
import org.openhab.core.io.rest.RESTConstants;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||||
|
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -48,6 +53,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Yannick Schaus - port to openHAB with modifications
|
* @author Yannick Schaus - port to openHAB with modifications
|
||||||
*/
|
*/
|
||||||
@Provider
|
@Provider
|
||||||
|
@Component
|
||||||
|
@JaxrsExtension
|
||||||
|
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||||
public class RolesAllowedDynamicFeatureImpl implements DynamicFeature {
|
public class RolesAllowedDynamicFeatureImpl implements DynamicFeature {
|
||||||
private final Logger logger = LoggerFactory.getLogger(RolesAllowedDynamicFeatureImpl.class);
|
private final Logger logger = LoggerFactory.getLogger(RolesAllowedDynamicFeatureImpl.class);
|
||||||
|
|
||||||
@ -114,7 +122,9 @@ public class RolesAllowedDynamicFeatureImpl implements DynamicFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
|
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
|
||||||
throw new NotAuthorizedException("User is not authenticated");
|
requestContext.abortWith(
|
||||||
|
JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final String role : rolesAllowed) {
|
for (final String role : rolesAllowed) {
|
||||||
@ -124,7 +134,8 @@ public class RolesAllowedDynamicFeatureImpl implements DynamicFeature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ForbiddenException("User is authenticated but doesn't have access to this resource");
|
requestContext.abortWith(JSONResponse.createErrorResponse(Status.FORBIDDEN,
|
||||||
|
"User is authenticated but doesn't have access to this resource"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
|
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
|
||||||
|
@ -24,8 +24,6 @@ import javax.ws.rs.Consumes;
|
|||||||
import javax.ws.rs.CookieParam;
|
import javax.ws.rs.CookieParam;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.NotAuthorizedException;
|
|
||||||
import javax.ws.rs.NotFoundException;
|
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
@ -47,6 +45,7 @@ import org.openhab.core.auth.PendingToken;
|
|||||||
import org.openhab.core.auth.User;
|
import org.openhab.core.auth.User;
|
||||||
import org.openhab.core.auth.UserRegistry;
|
import org.openhab.core.auth.UserRegistry;
|
||||||
import org.openhab.core.auth.UserSession;
|
import org.openhab.core.auth.UserSession;
|
||||||
|
import org.openhab.core.io.rest.JSONResponse;
|
||||||
import org.openhab.core.io.rest.RESTConstants;
|
import org.openhab.core.io.rest.RESTConstants;
|
||||||
import org.openhab.core.io.rest.RESTResource;
|
import org.openhab.core.io.rest.RESTResource;
|
||||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||||
@ -141,12 +140,12 @@ public class TokenResource implements RESTResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public Response getSessions(@Context SecurityContext securityContext) {
|
public Response getSessions(@Context SecurityContext securityContext) {
|
||||||
if (securityContext.getUserPrincipal() == null) {
|
if (securityContext.getUserPrincipal() == null) {
|
||||||
throw new NotAuthorizedException("User not authenticated");
|
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new NotFoundException("User not found");
|
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<UserSessionDTO> sessions = user.getSessions().stream().map(this::toUserSessionDTO);
|
Stream<UserSessionDTO> sessions = user.getSessions().stream().map(this::toUserSessionDTO);
|
||||||
@ -161,12 +160,12 @@ public class TokenResource implements RESTResource {
|
|||||||
public Response deleteSession(@FormParam("refresh_token") String refreshToken, @FormParam("id") String id,
|
public Response deleteSession(@FormParam("refresh_token") String refreshToken, @FormParam("id") String id,
|
||||||
@Context SecurityContext securityContext) {
|
@Context SecurityContext securityContext) {
|
||||||
if (securityContext.getUserPrincipal() == null) {
|
if (securityContext.getUserPrincipal() == null) {
|
||||||
throw new NotAuthorizedException("User not authenticated");
|
return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "User is not authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
ManagedUser user = (ManagedUser) userRegistry.get(securityContext.getUserPrincipal().getName());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new NotFoundException("User not found");
|
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<UserSession> session;
|
Optional<UserSession> session;
|
||||||
@ -178,7 +177,7 @@ public class TokenResource implements RESTResource {
|
|||||||
throw new IllegalArgumentException("no refresh_token or id specified");
|
throw new IllegalArgumentException("no refresh_token or id specified");
|
||||||
}
|
}
|
||||||
if (session.isEmpty()) {
|
if (session.isEmpty()) {
|
||||||
throw new NotFoundException("Session not found");
|
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Session not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseBuilder response = Response.ok();
|
ResponseBuilder response = Response.ok();
|
||||||
|
Loading…
Reference in New Issue
Block a user