]> git.basschouten.com Git - openhab-addons.git/blob
6aba374a51425cf0997bac392981c98bf490c3ee
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.io.hueemulation.internal.rest;
14
15 import java.time.LocalDateTime;
16 import java.time.format.DateTimeFormatter;
17 import java.util.List;
18 import java.util.Set;
19 import java.util.UUID;
20
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;
31
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;
51
52 import com.google.gson.reflect.TypeToken;
53
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;
57
58 /**
59  * Manages users of this emulated HUE bridge. Stores users in the frameworks storage backend.
60  * <p>
61  * This is an OSGi component. Usage:
62  *
63  * <pre>
64  * &#64;Reference
65  * UserManagement userManagment;
66  * </pre>
67  *
68  * @author David Graeff - Initial contribution
69  */
70 @Component(immediate = false, service = UserManagement.class)
71 @JaxrsResource
72 @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + HueEmulationService.REST_APP_NAME + ")")
73 @NonNullByDefault
74 @Path("")
75 @Produces(MediaType.APPLICATION_JSON)
76 public class UserManagement extends DefaultAbstractManagedProvider<HueUserAuthWithSecrets, String> {
77     private final Logger logger = LoggerFactory.getLogger(UserManagement.class);
78
79     protected final ConfigStore cs;
80
81     @Activate
82     public UserManagement(final @Reference StorageService storageService, final @Reference ConfigStore cs) {
83         super(storageService);
84         this.cs = cs;
85
86         for (HueUserAuthWithSecrets userAuth : getAll()) {
87             cs.ds.config.whitelist.put(userAuth.getUID(), userAuth);
88         }
89     }
90
91     /**
92      * Checks if the username exists in the whitelist
93      */
94     @SuppressWarnings("null")
95     public boolean authorizeUser(String userName) {
96         HueUserAuth userAuth = cs.ds.config.whitelist.get(userName);
97
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);
101         }
102
103         if (userAuth != null) {
104             userAuth.lastUseDate = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
105             update((HueUserAuthWithSecrets) userAuth);
106         }
107
108         return userAuth != null;
109     }
110
111     /**
112      * Adds a user to the whitelist and persist the user file
113      *
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
117      */
118     private void addUser(String apiKey, String clientKey, String label) {
119         if (cs.ds.config.whitelist.containsKey(apiKey)) {
120             return;
121         }
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,
125                 clientKey);
126         cs.ds.config.whitelist.put(apiKey, hueUserAuth);
127         add(hueUserAuth);
128     }
129
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);
135         }
136         remove(apiKey);
137     }
138
139     @Override
140     protected String getStorageName() {
141         return "hueEmulationUsers";
142     }
143
144     @Override
145     protected String keyToString(String key) {
146         return key;
147     }
148
149     @GET
150     public Response illegalGetUserAccessApi(@Context UriInfo uri) {
151         return NetworkUtils.singleError(cs.gson, uri, HueResponse.METHOD_NOT_ALLOWED, "Not Authorized");
152     }
153
154     @POST
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");
162         }
163
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");
169         }
170
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(Set.of(new HueResponse(h)), new TypeToken<List<?>>() {
176         }.getType());
177
178         return Response.ok(result).build();
179     }
180
181     @GET
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");
189         }
190         return Response.ok(cs.gson.toJson(cs.ds.config.whitelist.get(userid))).build();
191     }
192
193     @GET
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");
200         }
201         return Response.ok(cs.gson.toJson(cs.ds.config.whitelist)).build();
202     }
203
204     @DELETE
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");
214         }
215         if (!username.equals(id)) {
216             return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED,
217                     "You can only remove yourself not someone else!");
218         }
219
220         removeUser(username);
221
222         return NetworkUtils.singleSuccess(cs.gson, "/config/whitelist/" + username + " deleted.");
223     }
224 }