Skip to content

Commit

Permalink
Add required e-mail address config param
Browse files Browse the repository at this point in the history
Also-by: Bert Plonus <[email protected]>
Also-by: Martin Lepsy <[email protected]>
Also-by: Benjamin Bolte <[email protected]>
Signed-off-by: Björn Lange <[email protected]>
  • Loading branch information
Björn Lange committed May 21, 2021
1 parent 594c940 commit 7389bf9
Show file tree
Hide file tree
Showing 30 changed files with 612 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ private MieleCloudBindingConstants() {
*/
public static final String PROPERTY_ACCESS_TOKEN = "accessToken";

/**
* Name of the configuration parameter for the e-mail address.
*/
public static final String CONFIG_PARAM_EMAIL = "email";

/**
* Name of the configuration parameter for the locale. The locale is stored as a 2-letter language code.
*/
Expand Down Expand Up @@ -217,6 +222,7 @@ private I18NKeys() {
public static final String BRIDGE_STATUS_DESCRIPTION_ACCESS_TOKEN_NOT_CONFIGURED = "@text/mielecloud.bridge.status.access.token.not.configured";
public static final String BRIDGE_STATUS_DESCRIPTION_ACCOUNT_NOT_AUTHORIZED = "@text/mielecloud.bridge.status.account.not.authorized";
public static final String BRIDGE_STATUS_DESCRIPTION_ACCESS_TOKEN_REFRESH_FAILED = "@text/mielecloud.bridge.status.access.token.refresh.failed";
public static final String BRIDGE_STATUS_DESCRIPTION_INVALID_EMAIL = "@text/mielecloud.bridge.status.invalid.email";
public static final String BRIDGE_STATUS_DESCRIPTION_TRANSIENT_HTTP_ERROR = "@text/mielecloud.bridge.status.transient.http.error";

public static final String THING_STATUS_DESCRIPTION_WEBSERVICE_MISSING = "@text/mielecloud.thing.status.webservice.missing";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ public interface OAuthAuthorizationHandler {
* @param clientId Client ID.
* @param clientSecret Client secret.
* @param bridgeUid The UID of the bridge to authorize.
* @param email E-mail address identifying the account to authorize.
* @throws OngoingAuthorizationException if there already is an ongoing authorization.
*/
void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid);
void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid, String email);

/**
* Creates the authorization URL for the ongoing authorization.
Expand All @@ -51,6 +52,11 @@ public interface OAuthAuthorizationHandler {
*/
ThingUID getBridgeUid();

/**
* Gets the e-mail address associated with the account that is currently being authorized.
*/
String getEmail();

/**
* Completes the authorization by extracting the authorization code from the given redirection URL, fetching the
* access token response and persisting it. After this method succeeded the access token can be read from the
Expand All @@ -66,9 +72,9 @@ public interface OAuthAuthorizationHandler {
/**
* Gets the access token from persistent storage.
*
* @param bridgeUid UID of the bridge for which the access token is requested.
* @param email E-mail address for which the access token is requested.
* @return The access token.
* @throws OAuthException if the access token cannot be obtained.
*/
String getAccessToken(ThingUID bridgeUid);
String getAccessToken(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public final class OAuthAuthorizationHandlerImpl implements OAuthAuthorizationHa
@Nullable
private ThingUID bridgeUid;
@Nullable
private String email;
@Nullable
private String redirectUri;
@Nullable
private ScheduledFuture<?> timer;
Expand All @@ -68,14 +70,16 @@ public OAuthAuthorizationHandlerImpl(OAuthFactory oauthFactory, ScheduledExecuto
}

@Override
public synchronized void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid) {
public synchronized void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid,
String email) {
if (this.oauthClientService != null) {
throw new OngoingAuthorizationException("There is already an ongoing authorization!", timerExpiryTimestamp);
}

this.oauthClientService = oauthFactory.createOAuthClientService(bridgeUid.getAsString(), TOKEN_URL,
AUTHORIZATION_URL, clientId, clientSecret, null, false);
this.oauthClientService = oauthFactory.createOAuthClientService(email, TOKEN_URL, AUTHORIZATION_URL, clientId,
clientSecret, null, false);
this.bridgeUid = bridgeUid;
this.email = email;
redirectUri = null;
timer = null;
timerExpiryTimestamp = null;
Expand Down Expand Up @@ -109,6 +113,15 @@ public ThingUID getBridgeUid() {
return bridgeUid;
}

@Override
public String getEmail() {
final String email = this.email;
if (email == null) {
throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
}
return email;
}

@Override
public synchronized void completeAuthorization(String redirectUrlWithParameters) {
abortTimer();
Expand All @@ -132,6 +145,7 @@ public synchronized void completeAuthorization(String redirectUrlWithParameters)
} finally {
this.oauthClientService = null;
this.bridgeUid = null;
this.email = null;
this.redirectUri = null;
}
}
Expand Down Expand Up @@ -160,6 +174,7 @@ private void abortTimer() {
private synchronized void cancelAuthorization() {
oauthClientService = null;
bridgeUid = null;
email = null;
redirectUri = null;
final ScheduledFuture<?> timer = this.timer;
if (timer != null) {
Expand All @@ -170,10 +185,10 @@ private synchronized void cancelAuthorization() {
}

@Override
public String getAccessToken(ThingUID bridgeUid) {
OAuthClientService clientService = oauthFactory.getOAuthClientService(bridgeUid.getAsString());
public String getAccessToken(String email) {
OAuthClientService clientService = oauthFactory.getOAuthClientService(email);
if (clientService == null) {
throw new OAuthException("There is no access token registered for '" + bridgeUid.getAsString() + "'");
throw new OAuthException("There is no access token registered for '" + email + "'");
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,18 @@ public class ThingsTemplateGenerator {
* @param locale Locale for accessing the Miele cloud service.
* @return The template.
*/
public String createBridgeConfigurationTemplate(String bridgeId, String locale) {
return "Bridge " + MieleCloudBindingConstants.THING_TYPE_BRIDGE.getAsString() + ":" + bridgeId + " [ locale=\""
+ locale + "\" ]";
public String createBridgeConfigurationTemplate(String bridgeId, String email, String locale) {
var builder = new StringBuilder();
builder.append("Bridge ");
builder.append(MieleCloudBindingConstants.THING_TYPE_BRIDGE.getAsString());
builder.append(":");
builder.append(bridgeId);
builder.append(" [ email=\"");
builder.append(email);
builder.append("\", locale=\"");
builder.append(locale);
builder.append("\" ]");
return builder.toString();
}

/**
Expand All @@ -51,7 +60,8 @@ public String createBridgeAndThingConfigurationTemplate(Bridge bridge, List<Thin
List<DiscoveryResult> discoveryResults) {
StringBuilder result = new StringBuilder();
result.append(createBridgeConfigurationTemplate(bridge.getUID().getId(),
bridge.getConfiguration().get(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE).toString()));
bridge.getConfiguration().get(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL).toString(),
getLocale(bridge)));
result.append(" {\n");

for (Thing thing : pairedThings) {
Expand All @@ -66,6 +76,15 @@ public String createBridgeAndThingConfigurationTemplate(Bridge bridge, List<Thin
return result.toString();
}

private String getLocale(Bridge bridge) {
var locale = bridge.getConfiguration().get(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE);
if (locale instanceof String) {
return (String) locale;
} else {
return "en";
}
}

private String createThingConfigurationTemplate(Thing thing) {
StringBuilder result = new StringBuilder();
result.append("Thing ").append(thing.getThingTypeUID().getId()).append(" ").append(thing.getUID().getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ private String renderBridge(Thing bridge, int index) {
builder.append(thingUid.substring(0, thingUid.length() - thingId.length()));
builder.append(" ");
builder.append(thingId);
builder.append(" ");
builder.append(bridge.getConfiguration().get(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL).toString());
builder.append("\n");

builder.append(" <span class=\"status ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
package org.openhab.binding.mielecloud.internal.config.servlet;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;

Expand All @@ -23,6 +23,7 @@
import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
import org.openhab.binding.mielecloud.internal.config.exception.BridgeCreationFailedException;
import org.openhab.binding.mielecloud.internal.config.exception.BridgeReconfigurationFailedException;
import org.openhab.binding.mielecloud.internal.util.EmailValidator;
import org.openhab.binding.mielecloud.internal.util.LocaleValidator;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
Expand All @@ -47,6 +48,7 @@ public final class CreateBridgeServlet extends AbstractRedirectionServlet {

private static final String LOCALE_PARAMETER_NAME = "locale";
public static final String BRIDGE_UID_PARAMETER_NAME = "bridgeUid";
public static final String EMAIL_PARAMETER_NAME = "email";

private static final long serialVersionUID = -2912042079128722887L;

Expand Down Expand Up @@ -80,6 +82,12 @@ protected String getRedirectionDestination(HttpServletRequest request) {
return "/mielecloud/failure?" + FailureServlet.MISSING_BRIDGE_UID_PARAMETER_NAME + "=true";
}

String email = request.getParameter(EMAIL_PARAMETER_NAME);
if (email == null || email.isEmpty()) {
logger.warn("Cannot create bridge: E-mail address is missing.");
return "/mielecloud/failure?" + FailureServlet.MISSING_EMAIL_PARAMETER_NAME + "=true";
}

ThingUID bridgeUid = null;
try {
bridgeUid = new ThingUID(bridgeUidString);
Expand All @@ -88,34 +96,42 @@ protected String getRedirectionDestination(HttpServletRequest request) {
return "/mielecloud/failure?" + FailureServlet.MALFORMED_BRIDGE_UID_PARAMETER_NAME + "=true";
}

if (!EmailValidator.isValid(email)) {
logger.warn("Cannot create bridge: E-mail address '{}' is malformed.", email);
return "/mielecloud/failure?" + FailureServlet.MALFORMED_EMAIL_PARAMETER_NAME + "=true";
}

String locale = getValidLocale(request.getParameter(LOCALE_PARAMETER_NAME));

logger.debug("Auto configuring Miele account using locale '{}' (requested locale was '{}')", locale,
request.getParameter(LOCALE_PARAMETER_NAME));
try {
Thing bridge = pairOrReconfigureBridge(locale, bridgeUid);
Thing bridge = pairOrReconfigureBridge(locale, bridgeUid, email);
waitForBridgeToComeOnline(bridge);
return "/mielecloud";
} catch (BridgeReconfigurationFailedException e) {
logger.warn("{}", e.getMessage());
return "/mielecloud/success?" + SuccessServlet.BRIDGE_RECONFIGURATION_FAILED_PARAMETER_NAME + "=true&"
+ SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString;
+ SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
+ SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
} catch (BridgeCreationFailedException e) {
logger.warn("Thing creation failed because there was no binding available that supports the thing.");
return "/mielecloud/success?" + SuccessServlet.BRIDGE_CREATION_FAILED_PARAMETER_NAME + "=true&"
+ SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString;
+ SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
+ SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
}
}

private Thing pairOrReconfigureBridge(String locale, ThingUID bridgeUid) {
private Thing pairOrReconfigureBridge(String locale, ThingUID bridgeUid, String email) {
DiscoveryResult result = DiscoveryResultBuilder.create(bridgeUid)
.withRepresentationProperty(Thing.PROPERTY_MODEL_ID).withLabel(MIELE_CLOUD_BRIDGE_LABEL)
.withProperty(Thing.PROPERTY_MODEL_ID, MIELE_CLOUD_BRIDGE_NAME)
.withProperty(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale).build();
.withProperty(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale)
.withProperty(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL, email).build();
if (inbox.add(result)) {
return pairBridge(bridgeUid);
} else {
return reconfigureBridge(bridgeUid, locale);
return reconfigureBridge(bridgeUid, locale, email);
}
}

Expand All @@ -129,7 +145,7 @@ private Thing pairBridge(ThingUID thingUid) {
return thing;
}

private Thing reconfigureBridge(ThingUID thingUid, String locale) {
private Thing reconfigureBridge(ThingUID thingUid, String locale, String email) {
logger.debug("Thing already exists. Modifying configuration.");
Thing thing = thingRegistry.get(thingUid);
if (thing == null) {
Expand All @@ -144,7 +160,8 @@ private Thing reconfigureBridge(ThingUID thingUid, String locale) {

// calling handleConfigurationUpdate on MieleBridgeHandler always re-initializes the thing
handler.handleConfigurationUpdate(
Collections.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale));
Map.ofEntries(Map.entry(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale),
Map.entry(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL, email)));

return thing;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public class FailureServlet extends AbstractShowPageServlet {
public static final String NO_ONGOING_AUTHORIZATION_PARAMETER_NAME = "noOngoingAuthorization";
public static final String FAILED_TO_COMPLETE_AUTHORIZATION_PARAMETER_NAME = "failedToCompleteAuthorization";
public static final String MISSING_BRIDGE_UID_PARAMETER_NAME = "missingBridgeUid";
public static final String MISSING_EMAIL_PARAMETER_NAME = "missingEmail";
public static final String MALFORMED_BRIDGE_UID_PARAMETER_NAME = "malformedBridgeUid";
public static final String MALFORMED_EMAIL_PARAMETER_NAME = "malformedEmail";
public static final String MISSING_REQUEST_URL_PARAMETER_NAME = "missingRequestUrl";

public static final String OAUTH2_ERROR_ACCESS_DENIED = "access_denied";
Expand Down Expand Up @@ -74,8 +76,12 @@ private String getErrorMessage(HttpServletRequest request) {
return "Completing the final authorization request failed. Please try the config flow again.";
} else if (ServletUtil.isParameterEnabled(request, MISSING_BRIDGE_UID_PARAMETER_NAME)) {
return "Missing bridge UID.";
} else if (ServletUtil.isParameterEnabled(request, MISSING_EMAIL_PARAMETER_NAME)) {
return "Missing e-mail address.";
} else if (ServletUtil.isParameterEnabled(request, MALFORMED_BRIDGE_UID_PARAMETER_NAME)) {
return "Malformed bridge UID.";
} else if (ServletUtil.isParameterEnabled(request, MALFORMED_EMAIL_PARAMETER_NAME)) {
return "Malformed e-mail address.";
} else if (ServletUtil.isParameterEnabled(request, MISSING_REQUEST_URL_PARAMETER_NAME)) {
return "Missing request URL. Please try the config flow again.";
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
import org.openhab.binding.mielecloud.internal.config.OAuthAuthorizationHandler;
import org.openhab.binding.mielecloud.internal.config.exception.NoOngoingAuthorizationException;
import org.openhab.binding.mielecloud.internal.config.exception.OngoingAuthorizationException;
import org.openhab.binding.mielecloud.internal.util.EmailValidator;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Servlet gathers and processes required information to perform an authorization with the Miele cloud service
* and create a bridge afterwards. Required parameters are the client ID, client secret and an ID for the bridge. If the
* given parameters are valid, the browser is redirected to the Miele service login. Otherwise, the browser is
* redirected to the previous page with an according error message.
* and create a bridge afterwards. Required parameters are the client ID, client secret, an ID for the bridge and an
* e-mail address. If the given parameters are valid, the browser is redirected to the Miele service login. Otherwise,
* the browser is redirected to the previous page with an according error message.
*
* @author Björn Lange - Initial Contribution
*/
Expand All @@ -43,6 +44,7 @@ public final class ForwardToLoginServlet extends AbstractRedirectionServlet {
public static final String CLIENT_ID_PARAMETER_NAME = "clientId";
public static final String CLIENT_SECRET_PARAMETER_NAME = "clientSecret";
public static final String BRIDGE_ID_PARAMETER_NAME = "bridgeId";
public static final String EMAIL_PARAMETER_NAME = "email";

private final Logger logger = LoggerFactory.getLogger(ForwardToLoginServlet.class);

Expand All @@ -62,6 +64,7 @@ protected String getRedirectionDestination(HttpServletRequest request) {
String clientId = request.getParameter(CLIENT_ID_PARAMETER_NAME);
String clientSecret = request.getParameter(CLIENT_SECRET_PARAMETER_NAME);
String bridgeId = request.getParameter(BRIDGE_ID_PARAMETER_NAME);
String email = request.getParameter(EMAIL_PARAMETER_NAME);

if (clientId == null || clientId.isEmpty()) {
logger.warn("Request is missing client ID.");
Expand All @@ -75,6 +78,10 @@ protected String getRedirectionDestination(HttpServletRequest request) {
logger.warn("Request is missing bridge ID.");
return getErrorRedirectionUrl(PairAccountServlet.MISSING_BRIDGE_ID_PARAMETER_NAME);
}
if (email == null || email.isEmpty()) {
logger.warn("Request is missing e-mail address.");
return getErrorRedirectionUrl(PairAccountServlet.MISSING_EMAIL_PARAMETER_NAME);
}

ThingUID bridgeUid = null;
try {
Expand All @@ -84,8 +91,13 @@ protected String getRedirectionDestination(HttpServletRequest request) {
return getErrorRedirectionUrl(PairAccountServlet.MALFORMED_BRIDGE_ID_PARAMETER_NAME);
}

if (!EmailValidator.isValid(email)) {
logger.warn("Passed e-mail address '{}' is invalid.", email);
return getErrorRedirectionUrl(PairAccountServlet.MALFORMED_EMAIL_PARAMETER_NAME);
}

try {
authorizationHandler.beginAuthorization(clientId, clientSecret, bridgeUid);
authorizationHandler.beginAuthorization(clientId, clientSecret, bridgeUid, email);
} catch (OngoingAuthorizationException e) {
logger.warn("Cannot begin new authorization process while another one is still running.");
return getErrorRedirectUrlWithExpiryTime(e.getOngoingAuthorizationExpiryTimestamp());
Expand Down
Loading

0 comments on commit 7389bf9

Please sign in to comment.