]> git.basschouten.com Git - openhab-addons.git/blob
d2d5f66fe7c2c311065a7096d7198baae49416d8
[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.binding.nuki.internal.constants;
14
15 import java.net.URI;
16 import java.net.URLEncoder;
17 import java.nio.charset.StandardCharsets;
18 import java.security.MessageDigest;
19 import java.security.NoSuchAlgorithmException;
20 import java.security.SecureRandom;
21 import java.time.OffsetDateTime;
22 import java.time.ZoneOffset;
23 import java.time.format.DateTimeFormatter;
24 import java.util.Locale;
25
26 import javax.ws.rs.core.UriBuilder;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.openhab.core.util.HexUtils;
30
31 /**
32  * The {@link NukiLinkBuilder} class helps with constructing links to various Nuki APIs.
33  * Links to secured APIs will be created with all necessary authentication parameters.
34  *
35  * @author Jan Vybíral - Initial contribution
36  */
37 @NonNullByDefault
38 public class NukiLinkBuilder {
39     public static final URI URI_BRIDGE_DISCOVERY = URI.create("https://api.nuki.io/discover/bridges");
40     public static final String CALLBACK_ENDPOINT = "/nuki/bcb";
41
42     private static final String PATH_INFO = "/info";
43     private static final String PATH_AUTH = "/auth";
44     private static final String PATH_LOCKSTATE = "/lockState";
45     private static final String PATH_LOCKACTION = "/lockAction";
46     public static final String PATH_CBADD = "/callback/add";
47     public static final String PATH_CBLIST = "/callback/list";
48     public static final String PATH_CBREMOVE = "/callback/remove";
49     public static final String PATH_LIST = "/list";
50
51     private final String host;
52     private final int port;
53     private final String token;
54     private final boolean secureToken;
55     private final SecureRandom random = new SecureRandom();
56     private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX");
57
58     /**
59      * Create new instance of link builder
60      * 
61      * @param host Hostname/ip address of Nuki bridge
62      * @param port Port of Nuki bridge
63      * @param token Token for authenticating API calls
64      */
65     public NukiLinkBuilder(String host, int port, String token, boolean secureToken) {
66         this.host = host;
67         this.port = port;
68         this.token = token;
69         this.secureToken = secureToken;
70     }
71
72     public static URI getAuthUri(String host, int port) {
73         return UriBuilder.fromPath(PATH_AUTH).host(host).port(port).scheme("http").build();
74     }
75
76     private UriBuilder builder(String path) {
77         return UriBuilder.fromPath(path).scheme("http").host(host).port(port);
78     }
79
80     public URI info() {
81         return buildWithAuth(builder(PATH_INFO));
82     }
83
84     public URI lockState(String nukiId, int deviceType) {
85         return buildWithAuth(builder(PATH_LOCKSTATE).queryParam("nukiId", nukiId).queryParam("deviceType", deviceType));
86     }
87
88     public URI lockAction(String nukiId, int deviceType, int action) {
89         return buildWithAuth(builder(PATH_LOCKACTION).queryParam("deviceType", deviceType).queryParam("action", action)
90                 .queryParam("nukiId", nukiId));
91     }
92
93     public URI callbackList() {
94         return buildWithAuth(builder(PATH_CBLIST));
95     }
96
97     public URI callbackAdd(String callback) {
98         String callbackEncoded = URLEncoder.encode(callback, StandardCharsets.UTF_8);
99         return buildWithAuth(builder(PATH_CBADD).queryParam("url", callbackEncoded));
100     }
101
102     public URI callbackRemove(int id) {
103         return buildWithAuth(builder(PATH_CBREMOVE).queryParam("id", id));
104     }
105
106     public URI list() {
107         return buildWithAuth(builder(PATH_LIST));
108     }
109
110     public static UriBuilder callbackPath(String callbackId) {
111         return UriBuilder.fromPath(CALLBACK_ENDPOINT).queryParam("callbackId", callbackId);
112     }
113
114     public static URI callbackUri(String host, int port, String callbackId) {
115         return callbackPath(callbackId).host(host).port(port).scheme("http").build();
116     }
117
118     private URI buildWithAuth(UriBuilder builder) {
119         if (secureToken) {
120             return buildWithHashedToken(builder);
121         } else {
122             return buildWithPlainToken(builder);
123         }
124     }
125
126     private URI buildWithHashedToken(UriBuilder builder) {
127         String ts = formatter.format(OffsetDateTime.now(ZoneOffset.UTC));
128         Integer rnr = random.nextInt(65536);
129         String hashedToken = sha256(ts + "," + rnr + "," + token);
130
131         return builder.queryParam("ts", ts).queryParam("rnr", rnr).queryParam("hash", hashedToken).build();
132     }
133
134     private URI buildWithPlainToken(UriBuilder builder) {
135         return builder.queryParam("token", token).build();
136     }
137
138     public static String sha256(String data) {
139         MessageDigest digest;
140         try {
141             digest = MessageDigest.getInstance("SHA-256");
142         } catch (NoSuchAlgorithmException e) {
143             throw new IllegalStateException("SHA-256 Algorithm not supported", e);
144         }
145         byte[] rawHash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
146         return HexUtils.bytesToHex(rawHash).toLowerCase(Locale.ROOT);
147     }
148 }