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.binding.nuki.internal.constants;
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;
26 import javax.ws.rs.core.UriBuilder;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.openhab.core.util.HexUtils;
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.
35 * @author Jan Vybíral - Initial contribution
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";
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";
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");
59 * Create new instance of link builder
61 * @param host Hostname/ip address of Nuki bridge
62 * @param port Port of Nuki bridge
63 * @param token Token for authenticating API calls
65 public NukiLinkBuilder(String host, int port, String token, boolean secureToken) {
69 this.secureToken = secureToken;
72 public static URI getAuthUri(String host, int port) {
73 return UriBuilder.fromPath(PATH_AUTH).host(host).port(port).scheme("http").build();
76 private UriBuilder builder(String path) {
77 return UriBuilder.fromPath(path).scheme("http").host(host).port(port);
81 return buildWithAuth(builder(PATH_INFO));
84 public URI lockState(String nukiId, int deviceType) {
85 return buildWithAuth(builder(PATH_LOCKSTATE).queryParam("nukiId", nukiId).queryParam("deviceType", deviceType));
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));
93 public URI callbackList() {
94 return buildWithAuth(builder(PATH_CBLIST));
97 public URI callbackAdd(String callback) {
98 String callbackEncoded = URLEncoder.encode(callback, StandardCharsets.UTF_8);
99 return buildWithAuth(builder(PATH_CBADD).queryParam("url", callbackEncoded));
102 public URI callbackRemove(int id) {
103 return buildWithAuth(builder(PATH_CBREMOVE).queryParam("id", id));
107 return buildWithAuth(builder(PATH_LIST));
110 public static UriBuilder callbackPath(String callbackId) {
111 return UriBuilder.fromPath(CALLBACK_ENDPOINT).queryParam("callbackId", callbackId);
114 public static URI callbackUri(String host, int port, String callbackId) {
115 return callbackPath(callbackId).host(host).port(port).scheme("http").build();
118 private URI buildWithAuth(UriBuilder builder) {
120 return buildWithHashedToken(builder);
122 return buildWithPlainToken(builder);
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);
131 return builder.queryParam("ts", ts).queryParam("rnr", rnr).queryParam("hash", hashedToken).build();
134 private URI buildWithPlainToken(UriBuilder builder) {
135 return builder.queryParam("token", token).build();
138 public static String sha256(String data) {
139 MessageDigest digest;
141 digest = MessageDigest.getInstance("SHA-256");
142 } catch (NoSuchAlgorithmException e) {
143 throw new IllegalStateException("SHA-256 Algorithm not supported", e);
145 byte[] rawHash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
146 return HexUtils.bytesToHex(rawHash).toLowerCase(Locale.ROOT);