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.webexteams.internal;
15 import static org.openhab.binding.webexteams.internal.WebexTeamsBindingConstants.*;
17 import java.io.FileNotFoundException;
18 import java.io.IOException;
19 import java.io.InputStream;
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;
27 import java.util.Optional;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServlet;
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;
46 * The {@link WebexAuthService} class to manage the servlets and bind authorization servlet to bridges.
48 * @author Tom Deckers - Initial contribution
50 @Component(service = WebexAuthService.class, configurationPid = "binding.webexteams.authService")
52 public class WebexAuthService {
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";
58 private final Logger logger = LoggerFactory.getLogger(WebexAuthService.class);
60 private final List<WebexTeamsHandler> handlers = Collections.synchronizedList(new ArrayList<>());
62 private static final String ERROR_UKNOWN_BRIDGE = "Returned 'state' by oauth redirect doesn't match any accounts. Has the account been removed?";
64 private @NonNullByDefault({}) HttpService httpService;
65 private @NonNullByDefault({}) BundleContext bundleContext;
68 protected void activate(ComponentContext componentContext, Map<String, Object> properties) {
69 logger.debug("Activating WebexAuthService");
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);
81 protected void deactivate(ComponentContext componentContext) {
82 logger.debug("Deactivating WebexAuthService");
83 httpService.unregister(WEBEX_ALIAS);
84 httpService.unregister(WEBEX_ALIAS + WEBEX_RES_ALIAS);
88 * Creates a new {@link WebexAuthServlet}.
90 * @return the newly created servlet
91 * @throws IOException thrown when an HTML template could not be read
93 private HttpServlet createServlet() throws IOException {
94 return new WebexAuthServlet(this, readTemplate(TEMPLATE_INDEX), readTemplate(TEMPLATE_ACCOUNT));
98 * Reads a template from file and returns the content as String.
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
104 private String readTemplate(String templateName) throws IOException {
105 final URL index = bundleContext.getBundle().getEntry(templateName);
108 throw new FileNotFoundException(
109 String.format("Cannot find '{}' - failed to initialize Webex servlet", templateName));
111 try (InputStream inputStream = index.openStream()) {
112 return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
118 * Call with Webex redirect uri returned State and Code values to get the refresh and access tokens and persist
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
127 public String authorize(String servletBaseURL, String state, String code) throws WebexTeamsException {
128 logger.debug("Authorizing for state: {}, code: {}", state, code);
130 final WebexTeamsHandler listener = getWebexTeamsHandler(state);
132 if (listener == null) {
134 "Webex redirected with state '{}' but no matching account was found. Possible account has been removed.",
136 throw new WebexTeamsException(ERROR_UKNOWN_BRIDGE);
138 return listener.authorize(servletBaseURL, code);
143 * @param listener Adds the given handler
145 public void addWebexTeamsHandler(WebexTeamsHandler listener) {
146 if (!handlers.contains(listener)) {
147 handlers.add(listener);
152 * @param handler Removes the given handler
154 public void removeWebexTeamsHandler(WebexTeamsHandler handler) {
155 handlers.remove(handler);
159 * @return Returns all {@link WebexTeamsHandler}s.
161 public List<WebexTeamsHandler> getWebexTeamsHandlers() {
166 * Get the {@link WebexTeamsHandler} that matches the given thing UID.
168 * @param thingUID UID of the thing to match the handler with
169 * @return the {@link WebexTeamsHandler} matching the thing UID or null
171 private @Nullable WebexTeamsHandler getWebexTeamsHandler(String thingUID) {
172 final Optional<WebexTeamsHandler> maybeListener = handlers.stream().filter(l -> l.equalsThingUID(thingUID))
174 return maybeListener.isPresent() ? maybeListener.get() : null;
178 protected void setHttpService(HttpService httpService) {
179 this.httpService = httpService;
182 protected void unsetHttpService(HttpService httpService) {
183 this.httpService = null;