2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.hueemulation.internal.rest;
15 import java.time.LocalDateTime;
16 import java.time.format.DateTimeFormatter;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.UUID;
21 import javax.ws.rs.DELETE;
22 import javax.ws.rs.GET;
23 import javax.ws.rs.POST;
24 import javax.ws.rs.Path;
25 import javax.ws.rs.PathParam;
26 import javax.ws.rs.Produces;
27 import javax.ws.rs.core.Context;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.UriInfo;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.openhab.core.common.registry.DefaultAbstractManagedProvider;
34 import org.openhab.core.storage.StorageService;
35 import org.openhab.io.hueemulation.internal.ConfigStore;
36 import org.openhab.io.hueemulation.internal.HueEmulationService;
37 import org.openhab.io.hueemulation.internal.NetworkUtils;
38 import org.openhab.io.hueemulation.internal.dto.HueUserAuth;
39 import org.openhab.io.hueemulation.internal.dto.HueUserAuthWithSecrets;
40 import org.openhab.io.hueemulation.internal.dto.changerequest.HueCreateUser;
41 import org.openhab.io.hueemulation.internal.dto.response.HueResponse;
42 import org.openhab.io.hueemulation.internal.dto.response.HueSuccessResponseCreateUser;
43 import org.osgi.service.component.annotations.Activate;
44 import org.osgi.service.component.annotations.Component;
45 import org.osgi.service.component.annotations.Reference;
46 import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
47 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
48 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.google.gson.reflect.TypeToken;
54 import io.swagger.v3.oas.annotations.Operation;
55 import io.swagger.v3.oas.annotations.Parameter;
56 import io.swagger.v3.oas.annotations.responses.ApiResponse;
59 * Manages users of this emulated HUE bridge. Stores users in the frameworks storage backend.
61 * This is an OSGi component. Usage:
65 * UserManagement userManagment;
68 * @author David Graeff - Initial contribution
70 @Component(immediate = false, service = UserManagement.class)
72 @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + HueEmulationService.REST_APP_NAME + ")")
75 @Produces(MediaType.APPLICATION_JSON)
76 public class UserManagement extends DefaultAbstractManagedProvider<HueUserAuthWithSecrets, String> {
77 private final Logger logger = LoggerFactory.getLogger(UserManagement.class);
79 protected final ConfigStore cs;
82 public UserManagement(final @Reference StorageService storageService, final @Reference ConfigStore cs) {
83 super(storageService);
86 for (HueUserAuthWithSecrets userAuth : getAll()) {
87 cs.ds.config.whitelist.put(userAuth.getUID(), userAuth);
92 * Checks if the username exists in the whitelist
94 @SuppressWarnings("null")
95 public boolean authorizeUser(String userName) {
96 HueUserAuth userAuth = cs.ds.config.whitelist.get(userName);
98 if (cs.ds.config.linkbutton && cs.ds.config.createNewUserOnEveryEndpoint) {
99 addUser(userName, userName, "On-the-go-user");
100 userAuth = cs.ds.config.whitelist.get(userName);
103 if (userAuth != null) {
104 userAuth.lastUseDate = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
105 update((HueUserAuthWithSecrets) userAuth);
108 return userAuth != null;
112 * Adds a user to the whitelist and persist the user file
114 * @param apiKey The hue "username" which is actually an API key
115 * @param clientKey The UDP/DTLS client key
116 * @param The user visible name
118 private void addUser(String apiKey, String clientKey, String label) {
119 if (cs.ds.config.whitelist.containsKey(apiKey)) {
122 logger.debug("APIKey {} added", apiKey);
123 String l[] = label.split("#");
124 HueUserAuthWithSecrets hueUserAuth = new HueUserAuthWithSecrets(l[0], l.length == 2 ? l[1] : "openhab", apiKey,
126 cs.ds.config.whitelist.put(apiKey, hueUserAuth);
130 @SuppressWarnings("null")
131 private synchronized void removeUser(String apiKey) {
132 HueUserAuth userAuth = cs.ds.config.whitelist.remove(apiKey);
133 if (userAuth != null) {
134 logger.debug("APIKey {} removed", apiKey);
140 protected String getStorageName() {
141 return "hueEmulationUsers";
145 protected String keyToString(String key) {
150 public Response illegalGetUserAccessApi(@Context UriInfo uri) {
151 return NetworkUtils.singleError(cs.gson, uri, HueResponse.METHOD_NOT_ALLOWED, "Not Authorized");
155 @Operation(summary = "Create an API Key", responses = {
156 @ApiResponse(responseCode = "200", description = "API Key created"),
157 @ApiResponse(responseCode = "403", description = "Link button not pressed") })
158 public Response createNewUser(@Context UriInfo uri, String body) {
159 if (!cs.ds.config.linkbutton) {
160 return NetworkUtils.singleError(cs.gson, uri, HueResponse.LINK_BUTTON_NOT_PRESSED,
161 "link button not pressed");
164 final HueCreateUser userRequest;
165 userRequest = cs.gson.fromJson(body, HueCreateUser.class);
166 if (userRequest.devicetype.isEmpty()) {
167 return NetworkUtils.singleError(cs.gson, uri, HueResponse.INVALID_JSON,
168 "Invalid request: No devicetype set");
171 String apiKey = UUID.randomUUID().toString();
172 String clientKey = UUID.randomUUID().toString();
173 addUser(apiKey, clientKey, userRequest.devicetype);
174 HueSuccessResponseCreateUser h = new HueSuccessResponseCreateUser(apiKey, clientKey);
175 String result = cs.gson.toJson(Collections.singleton(new HueResponse(h)), new TypeToken<List<?>>() {
178 return Response.ok(result).build();
182 @Path("{username}/config/whitelist/{userid}")
183 @Operation(summary = "Return a user", responses = { @ApiResponse(responseCode = "200", description = "OK") })
184 public Response getUserApi(@Context UriInfo uri,
185 @PathParam("username") @Parameter(description = "username") String username,
186 @PathParam("userid") @Parameter(description = "User ID") String userid) {
187 if (!authorizeUser(username)) {
188 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
190 return Response.ok(cs.gson.toJson(cs.ds.config.whitelist.get(userid))).build();
194 @Path("{username}/config/whitelist")
195 @Operation(summary = "Return all users", responses = { @ApiResponse(responseCode = "200", description = "OK") })
196 public Response getAllUsersApi(@Context UriInfo uri,
197 @PathParam("username") @Parameter(description = "username") String username) {
198 if (!authorizeUser(username)) {
199 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
201 return Response.ok(cs.gson.toJson(cs.ds.config.whitelist)).build();
205 @Path("{username}/config/whitelist/{id}")
206 @Operation(summary = "Deletes a user", responses = {
207 @ApiResponse(responseCode = "200", description = "The user got removed"),
208 @ApiResponse(responseCode = "403", description = "Access denied") })
209 public Response removeUserApi(@Context UriInfo uri,
210 @PathParam("username") @Parameter(description = "username") String username,
211 @PathParam("id") @Parameter(description = "User to remove") String id) {
212 if (!authorizeUser(username)) {
213 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
215 if (!username.equals(id)) {
216 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED,
217 "You can only remove yourself not someone else!");
220 removeUser(username);
222 return NetworkUtils.singleSuccess(cs.gson, "/config/whitelist/" + username + " deleted.");