]> git.basschouten.com Git - openhab-addons.git/blob
21ec0db9d9bb1a0e400a9599ddef4729c0539cc0
[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.freeboxos.internal.api.rest;
14
15 import static javax.xml.bind.DatatypeConverter.printHexBinary;
16
17 import java.security.InvalidKeyException;
18 import java.security.NoSuchAlgorithmException;
19 import java.util.Map;
20 import java.util.Optional;
21
22 import javax.crypto.Mac;
23 import javax.crypto.spec.SecretKeySpec;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
28 import org.openhab.binding.freeboxos.internal.api.Response;
29 import org.osgi.framework.Bundle;
30 import org.osgi.framework.FrameworkUtil;
31
32 /**
33  * The {@link LoginManager} is the Java class used to handle api requests related to session handling and login
34  *
35  * @author GaĆ«l L'hopital - Initial contribution
36  */
37 @NonNullByDefault
38 public class LoginManager extends RestManager {
39     private static final Bundle BUNDLE = FrameworkUtil.getBundle(LoginManager.class);
40     private static final String APP_ID = BUNDLE.getSymbolicName();
41     private static final String ALGORITHM = "HmacSHA1";
42     private static final String PATH = "login";
43     private static final String SESSION = "session";
44     private static final String AUTHORIZE_ACTION = "authorize";
45     private static final String LOGOUT = "logout";
46
47     private enum Status {
48         PENDING, // the user has not confirmed the autorization request yet
49         TIMEOUT, // the user did not confirmed the authorization within the given time
50         GRANTED, // the app_token is valid and can be used to open a session
51         DENIED, // the user denied the authorization request
52         UNKNOWN // the app_token is invalid or has been revoked
53     }
54
55     private static record AuthorizationStatus(Status status, boolean loggedIn, String challenge,
56             @Nullable String passwordSalt, boolean passwordSet) {
57     }
58
59     private static class AuthStatus extends Response<AuthorizationStatus> {
60     }
61
62     private static record Authorization(String appToken, String trackId) {
63     }
64
65     private static class AuthResponse extends Response<Authorization> {
66     }
67
68     public enum Permission {
69         PARENTAL,
70         CONTACTS,
71         EXPLORER,
72         TV,
73         WDO,
74         DOWNLOADER,
75         PROFILE,
76         CAMERA,
77         SETTINGS,
78         CALLS,
79         HOME,
80         PVR,
81         VM,
82         PLAYER,
83         NONE,
84         UNKNOWN
85     }
86
87     public static record Session(Map<LoginManager.Permission, @Nullable Boolean> permissions,
88             @Nullable String sessionToken) {
89         protected boolean hasPermission(LoginManager.Permission checked) {
90             return Boolean.TRUE.equals(permissions.get(checked));
91         }
92     }
93
94     private static class SessionResponse extends Response<Session> {
95     }
96
97     private static record AuthorizeData(String appId, String appName, String appVersion, String deviceName) {
98         AuthorizeData(String appId, Bundle bundle) {
99             this(appId, bundle.getHeaders().get("Bundle-Name"), bundle.getVersion().toString(),
100                     bundle.getHeaders().get("Bundle-Vendor"));
101         }
102     }
103
104     private static record OpenSessionData(String appId, String password) {
105     }
106
107     private final Mac mac;
108     private Optional<Authorization> authorize = Optional.empty();
109
110     public LoginManager(FreeboxOsSession session) throws FreeboxException {
111         super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(PATH));
112         try {
113             this.mac = Mac.getInstance(ALGORITHM);
114         } catch (NoSuchAlgorithmException e) {
115             throw new IllegalArgumentException(e);
116         }
117     }
118
119     public Session openSession(String appToken) throws FreeboxException {
120         AuthorizationStatus authorization = getSingle(AuthStatus.class);
121
122         try {
123             // Initialize mac with the signing key
124             mac.init(new SecretKeySpec(appToken.getBytes(), mac.getAlgorithm()));
125             // Compute the hmac on input data bytes
126             byte[] rawHmac = mac.doFinal(authorization.challenge().getBytes());
127             // Convert raw bytes to Hex
128             String password = printHexBinary(rawHmac).toLowerCase();
129             return post(new OpenSessionData(APP_ID, password), SessionResponse.class, SESSION);
130         } catch (InvalidKeyException e) {
131             throw new IllegalArgumentException(e);
132         }
133     }
134
135     public void closeSession() throws FreeboxException {
136         post(LOGOUT);
137     }
138
139     public String checkGrantStatus() throws FreeboxException {
140         if (authorize.isEmpty()) {
141             authorize = Optional.of(post(new AuthorizeData(APP_ID, BUNDLE), AuthResponse.class, AUTHORIZE_ACTION));
142         }
143
144         return switch (getSingle(AuthStatus.class, AUTHORIZE_ACTION, authorize.get().trackId).status()) {
145             case PENDING -> "";
146             case GRANTED -> {
147                 String appToken = authorize.get().appToken;
148                 authorize = Optional.empty();
149                 yield appToken;
150             }
151             case TIMEOUT -> throw new FreeboxException("Unable to grant session, delay expired");
152             case DENIED -> throw new FreeboxException("Unable to grant session, access was denied");
153             case UNKNOWN -> throw new FreeboxException("Unable to grant session");
154         };
155     }
156 }