]> git.basschouten.com Git - openhab-addons.git/blob
881b0f0098900d2c46f4ae9f9e7f29326d4b5d8b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.webexteams.internal;
14
15 import static org.openhab.binding.webexteams.internal.WebexTeamsBindingConstants.*;
16
17 import java.io.FileNotFoundException;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.net.URL;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Hashtable;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServlet;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.osgi.framework.BundleContext;
35 import org.osgi.service.component.ComponentContext;
36 import org.osgi.service.component.annotations.Activate;
37 import org.osgi.service.component.annotations.Component;
38 import org.osgi.service.component.annotations.Deactivate;
39 import org.osgi.service.component.annotations.Reference;
40 import org.osgi.service.http.HttpService;
41 import org.osgi.service.http.NamespaceException;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link WebexAuthService} class to manage the servlets and bind authorization servlet to bridges.
47  * 
48  * @author Tom Deckers - Initial contribution
49  */
50 @Component(service = WebexAuthService.class, configurationPid = "binding.webexteams.authService")
51 @NonNullByDefault
52 public class WebexAuthService {
53
54     private static final String TEMPLATE_PATH = "templates/";
55     private static final String TEMPLATE_ACCOUNT = TEMPLATE_PATH + "account.html";
56     private static final String TEMPLATE_INDEX = TEMPLATE_PATH + "index.html";
57
58     private final Logger logger = LoggerFactory.getLogger(WebexAuthService.class);
59
60     private final List<WebexTeamsHandler> handlers = Collections.synchronizedList(new ArrayList<>());
61
62     private static final String ERROR_UKNOWN_BRIDGE = "Returned 'state' by oauth redirect doesn't match any accounts. Has the account been removed?";
63
64     private @NonNullByDefault({}) HttpService httpService;
65     private @NonNullByDefault({}) BundleContext bundleContext;
66
67     @Activate
68     protected void activate(ComponentContext componentContext, Map<String, Object> properties) {
69         logger.debug("Activating WebexAuthService");
70         try {
71             bundleContext = componentContext.getBundleContext();
72             httpService.registerServlet(WEBEX_ALIAS, createServlet(), new Hashtable<>(),
73                     httpService.createDefaultHttpContext());
74             httpService.registerResources(WEBEX_ALIAS + WEBEX_RES_ALIAS, "web", null);
75         } catch (NamespaceException | ServletException | IOException e) {
76             logger.warn("Error during webex auth servlet startup", e);
77         }
78     }
79
80     @Deactivate
81     protected void deactivate(ComponentContext componentContext) {
82         logger.debug("Deactivating WebexAuthService");
83         httpService.unregister(WEBEX_ALIAS);
84         httpService.unregister(WEBEX_ALIAS + WEBEX_RES_ALIAS);
85     }
86
87     /**
88      * Creates a new {@link WebexAuthServlet}.
89      *
90      * @return the newly created servlet
91      * @throws IOException thrown when an HTML template could not be read
92      */
93     private HttpServlet createServlet() throws IOException {
94         return new WebexAuthServlet(this, readTemplate(TEMPLATE_INDEX), readTemplate(TEMPLATE_ACCOUNT));
95     }
96
97     /**
98      * Reads a template from file and returns the content as String.
99      *
100      * @param templateName name of the template file to read
101      * @return The content of the template file
102      * @throws IOException thrown when an HTML template could not be read
103      */
104     private String readTemplate(String templateName) throws IOException {
105         final URL index = bundleContext.getBundle().getEntry(templateName);
106
107         if (index == null) {
108             throw new FileNotFoundException(
109                     String.format("Cannot find '{}' - failed to initialize Webex servlet", templateName));
110         } else {
111             try (InputStream inputStream = index.openStream()) {
112                 return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
113             }
114         }
115     }
116
117     /**
118      * Call with Webex redirect uri returned State and Code values to get the refresh and access tokens and persist
119      * these values
120      *
121      * @param servletBaseURL the servlet base, which will be the Webex redirect url
122      * @param state The Webex returned state value
123      * @param code The Webex returned code value
124      * @return returns the name of the Webex user that is authorized
125      * @throws WebexTeamsException if no handler was found for the state
126      */
127     public String authorize(String servletBaseURL, String state, String code) throws WebexTeamsException {
128         logger.debug("Authorizing for state: {}, code: {}", state, code);
129
130         final WebexTeamsHandler listener = getWebexTeamsHandler(state);
131
132         if (listener == null) {
133             logger.debug(
134                     "Webex redirected with state '{}' but no matching account was found. Possible account has been removed.",
135                     state);
136             throw new WebexTeamsException(ERROR_UKNOWN_BRIDGE);
137         } else {
138             return listener.authorize(servletBaseURL, code);
139         }
140     }
141
142     /**
143      * @param listener Adds the given handler
144      */
145     public void addWebexTeamsHandler(WebexTeamsHandler listener) {
146         if (!handlers.contains(listener)) {
147             handlers.add(listener);
148         }
149     }
150
151     /**
152      * @param handler Removes the given handler
153      */
154     public void removeWebexTeamsHandler(WebexTeamsHandler handler) {
155         handlers.remove(handler);
156     }
157
158     /**
159      * @return Returns all {@link WebexTeamsHandler}s.
160      */
161     public List<WebexTeamsHandler> getWebexTeamsHandlers() {
162         return handlers;
163     }
164
165     /**
166      * Get the {@link WebexTeamsHandler} that matches the given thing UID.
167      *
168      * @param thingUID UID of the thing to match the handler with
169      * @return the {@link WebexTeamsHandler} matching the thing UID or null
170      */
171     private @Nullable WebexTeamsHandler getWebexTeamsHandler(String thingUID) {
172         final Optional<WebexTeamsHandler> maybeListener = handlers.stream().filter(l -> l.equalsThingUID(thingUID))
173                 .findFirst();
174         return maybeListener.isPresent() ? maybeListener.get() : null;
175     }
176
177     @Reference
178     protected void setHttpService(HttpService httpService) {
179         this.httpService = httpService;
180     }
181
182     protected void unsetHttpService(HttpService httpService) {
183         this.httpService = null;
184     }
185 }