Skip to content

Commit

Permalink
Merge pull request #280 from sphereio/basic-auth
Browse files Browse the repository at this point in the history
Basic auth, closes #244
  • Loading branch information
lauraluiz committed Feb 5, 2016
2 parents c9e5611 + a3faa57 commit 52ac5ce
Show file tree
Hide file tree
Showing 46 changed files with 634 additions and 188 deletions.
7 changes: 7 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
"description": "The product attributes that allow to identify and select a product variant in your project. Enter the attribute keys separated by commas."
},

"BASIC_AUTH_CREDENTIALS": {
"description": "Leave empty if you do not want to enable basic access authentication! If you only want authorized users to access your website, please enter here the required credentials separated by colon ':'. For example, for a username 'john' and password 'secret', enter 'john:secret'"
},
"BASIC_AUTH_REALM": {
"description": "Authentication realm that identifies your application. This is only necessary when basic authentication is enabled.",
"value": "Sunrise Demo"
},
"APPLICATION_SECRET": {
"description": "The secret key is used to secure cryptographics functions. Anyone that can get access to the application secret will be able to generate any session they please, effectively allowing them to log in to your system as any user they please. If you deploy your application to several instances be sure to use the same key.",
"generator": "secret"
Expand Down
39 changes: 39 additions & 0 deletions app/basicauth/BasicAuth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package basicauth;

import io.sphere.sdk.models.Base;

import java.util.Base64;

/**
* Contains information related to the HTTP basic access authentication.
*/
public class BasicAuth extends Base {
private final String realm;
private final String encodedCredentials;

private BasicAuth(final String realm, final String encodedCredentials) {
this.realm = realm;
this.encodedCredentials = encodedCredentials;
}

public String getRealm() {
return realm;
}

/**
* Decides whether the authentication header is valid, i.e. it is equal to "Basic username:password",
* where "username:password" is encoded in Base64 scheme.
* @param authorizationHeader the contents of the HTTP Authorization header
* @return true if the header complies with an Authorization header and contains the correct credentials,
* false otherwise
*/
public boolean isAuthorized(final String authorizationHeader) {
final String expectedAuthHeader = "Basic " + encodedCredentials;
return expectedAuthHeader.equals(authorizationHeader);
}

public static BasicAuth of(final String realm, final String credentials) {
final String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
return new BasicAuth(realm, encodedCredentials);
}
}
16 changes: 16 additions & 0 deletions app/basicauth/BasicAuthProductionModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package basicauth;

import com.google.inject.AbstractModule;

import javax.inject.Singleton;

/**
* Configuration for the Guice {@link com.google.inject.Injector} which shall be used in production.
*/
public class BasicAuthProductionModule extends AbstractModule {

@Override
protected void configure() {
bind(BasicAuth.class).toProvider(BasicAuthProvider.class).in(Singleton.class);
}
}
39 changes: 39 additions & 0 deletions app/basicauth/BasicAuthProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package basicauth;

import inject.SunriseInitializationException;
import play.Configuration;
import play.Logger;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;

class BasicAuthProvider implements Provider<BasicAuth> {
public static final String CONFIG_REALM = "application.auth.realm";
public static final String CONFIG_CREDENTIALS = "application.auth.credentials";
public static final String REGEX_CREDENTIALS = "^[^ :]+:[^ :]+$";
private final Configuration configuration;

@Inject
public BasicAuthProvider(final Configuration configuration) {
this.configuration = configuration;
}

@Nullable
@Override
public BasicAuth get() {
final String realm = configuration.getString(CONFIG_REALM, "Sunrise Authentication");
final String credentials = configuration.getString(CONFIG_CREDENTIALS);
if (credentials != null && !credentials.isEmpty()) {
if (credentials.matches(REGEX_CREDENTIALS)) {
Logger.debug("Basic authentication: enabled for realm \"{}\"", realm);
return BasicAuth.of(realm, credentials);
} else {
throw new SunriseInitializationException("Basic access authentication credentials must be of the form 'username:password', matching: " + REGEX_CREDENTIALS);
}
} else {
Logger.debug("Basic authentication: disabled");
return null;
}
}
}
66 changes: 66 additions & 0 deletions app/basicauth/BasicAuthRequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package basicauth;

import play.Logger;
import play.http.DefaultHttpRequestHandler;
import play.libs.F;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.lang.reflect.Method;
import java.util.Optional;

import static play.mvc.Http.HeaderNames.AUTHORIZATION;
import static play.mvc.Http.HeaderNames.WWW_AUTHENTICATE;

/**
* Request handler that enables HTTP basic access authentication.
*/
public class BasicAuthRequestHandler extends DefaultHttpRequestHandler {
private static final Logger.ALogger LOGGER = Logger.of(BasicAuthRequestHandler.class);
private final Optional<BasicAuth> basicAuth;

@Inject
public BasicAuthRequestHandler(@Nullable final BasicAuth basicAuth) {
this.basicAuth = Optional.ofNullable(basicAuth);
}

@Override
public Action createAction(final Http.Request request, final Method actionMethod) {
if (basicAuth.isPresent()) {
return authenticate(basicAuth.get());
} else {
return super.createAction(request, actionMethod);
}
}

private Action authenticate(final BasicAuth basicAuth) {
return new Action.Simple() {

@Override
public F.Promise<Result> call(final Http.Context ctx) throws Throwable {
final boolean isAuthorized;
final String authorizationHeader = ctx.request().getHeader(AUTHORIZATION);
if (authorizationHeader != null) {
isAuthorized = basicAuth.isAuthorized(authorizationHeader);
} else {
isAuthorized = false;
ctx.response().setHeader(WWW_AUTHENTICATE, "Basic realm=\"" + basicAuth.getRealm() + "\"");
}
return authenticationResult(ctx, isAuthorized);
}

private F.Promise<Result> authenticationResult(final Http.Context ctx, final boolean isAuthorized) throws Throwable {
if (isAuthorized) {
LOGGER.debug("Authorized");
return delegate.call(ctx);
} else {
LOGGER.info("Unauthorized");
return F.Promise.pure(unauthorized());
}
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package controllers;
package categorytree;

import io.sphere.sdk.categories.CategoryTreeExtended;
import play.mvc.Controller;
Expand Down
14 changes: 14 additions & 0 deletions app/categorytree/CategoryTreeProductionModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package categorytree;

import com.google.inject.AbstractModule;
import io.sphere.sdk.categories.CategoryTreeExtended;

import javax.inject.Singleton;

public class CategoryTreeProductionModule extends AbstractModule {

@Override
protected void configure() {
bind(CategoryTreeExtended.class).toProvider(CategoryTreeProvider.class).in(Singleton.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inject;
package categorytree;

import com.google.inject.Provider;
import controllers.RefreshableCategoryTree;
import inject.SunriseInitializationException;
import io.sphere.sdk.categories.CategoryTreeExtended;
import io.sphere.sdk.client.SphereClient;
import play.Logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package controllers;
package categorytree;

import io.sphere.sdk.categories.Category;
import io.sphere.sdk.categories.CategoryTree;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package inject;
package ctpclient;

import com.google.inject.AbstractModule;
import io.sphere.sdk.client.PlayJavaSphereClient;
Expand All @@ -7,8 +7,7 @@
import javax.inject.Singleton;

/**
* Configuration for the Guice {@link com.google.inject.Injector} which
* shall be used in production and integration tests.
* Configuration for the Guice {@link com.google.inject.Injector} which shall be used in production.
*/
public class CtpClientProductionModule extends AbstractModule {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package inject;
package ctpclient;

import com.google.inject.Provider;
import io.sphere.sdk.client.PlayJavaSphereClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package inject;
package ctpclient;

import com.google.inject.Provider;
import io.sphere.sdk.client.*;
Expand Down
6 changes: 1 addition & 5 deletions app/inject/ApplicationProductionModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@

import com.google.inject.AbstractModule;
import common.cms.CmsService;
import common.controllers.ReverseRouter;
import common.i18n.I18nResolver;
import common.templates.TemplateService;
import pages.ReverseRouterImpl;

import javax.inject.Singleton;

/**
* Configuration for the Guice {@link com.google.inject.Injector} which
* shall be used in production and integration tests.
* Configuration for the Guice {@link com.google.inject.Injector} which shall be used in production.
*/
public class ApplicationProductionModule extends AbstractModule {

Expand All @@ -20,6 +17,5 @@ protected void configure() {
bind(I18nResolver.class).toProvider(I18nResolverProvider.class).in(Singleton.class);
bind(TemplateService.class).toProvider(TemplateServiceProvider.class).in(Singleton.class);
bind(CmsService.class).toProvider(CmsServiceProvider.class).in(Singleton.class);
bind(ReverseRouter.class).toInstance(new ReverseRouterImpl());
}
}
5 changes: 1 addition & 4 deletions app/inject/CtpModelsProductionModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@
import com.google.inject.AbstractModule;
import common.contexts.ProjectContext;
import common.models.ProductDataConfig;
import io.sphere.sdk.categories.CategoryTreeExtended;
import productcatalog.services.ProductService;
import shoppingcart.checkout.shipping.ShippingMethods;

import javax.inject.Singleton;

/**
* Configuration for the Guice {@link com.google.inject.Injector} which
* shall be used in production and integration tests.
* Configuration for the Guice {@link com.google.inject.Injector} which shall be used in production.
*/
public class CtpModelsProductionModule extends AbstractModule {

@Override
protected void configure() {
bind(ProjectContext.class).toProvider(ProjectContextProvider.class).in(Singleton.class);
bind(CategoryTreeExtended.class).toProvider(CategoryTreeProvider.class).in(Singleton.class);
bind(ProductDataConfig.class).toProvider(ProductDataConfigProvider.class).in(Singleton.class);
bind(ProductService.class).toProvider(ProductServiceProvider.class).in(Singleton.class);
bind(ShippingMethods.class).toProvider(ShippingMethodsProvider.class).in(Singleton.class);
Expand Down
1 change: 1 addition & 0 deletions app/inject/ProjectContextProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.inject.Provider;
import com.neovisionaries.i18n.CountryCode;
import common.contexts.ProjectContext;
import inject.SunriseInitializationException;
import io.sphere.sdk.client.SphereClient;
import io.sphere.sdk.projects.Project;
import io.sphere.sdk.projects.queries.ProjectGet;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pages;
package reverserouter;

import common.controllers.ReverseRouter;
import io.sphere.sdk.models.Base;
Expand Down
12 changes: 12 additions & 0 deletions app/reverserouter/ReverseRouterProductionModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package reverserouter;

import com.google.inject.AbstractModule;
import common.controllers.ReverseRouter;

public class ReverseRouterProductionModule extends AbstractModule {

@Override
protected void configure() {
bind(ReverseRouter.class).toInstance(new ReverseRouterImpl());
}
}
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ lazy val commonSettings = testSettings ++ releaseSettings ++ Seq (
Resolver.mavenLocal
),
libraryDependencies ++= Seq (
filters,
"io.commercetools" % "commercetools-sunrise-design" % sunriseDesignVersion,
"io.sphere.sdk.jvm" % "sphere-models" % sphereJvmSdkVersion,
"io.sphere.sdk.jvm" % "sphere-play-2_4-java-client_2.10" % sphereJvmSdkVersion,
"org.webjars" % "webjars-play_2.10" % "2.4.0-1",
"com.github.jknack" % "handlebars" % "4.0.3",
filters,
"commons-beanutils" % "commons-beanutils" % "1.9.2",
"commons-io" % "commons-io" % "2.4"
),
Expand Down Expand Up @@ -111,6 +111,7 @@ lazy val testScopes = "test,it,pt"
lazy val testSettings = Defaults.itSettings ++ inConfig(PlayTest)(Defaults.testSettings) ++ testDirConfigs(IntegrationTest, "it") ++ testDirConfigs(PlayTest, "pt") ++ Seq (
testOptions += Tests.Argument(TestFrameworks.JUnit, "-v"),
libraryDependencies ++= Seq (
javaWs % "pt",
"org.assertj" % "assertj-core" % "3.0.0" % testScopes,
PlayImport.component("play-test") % "it,pt"
),
Expand Down
8 changes: 5 additions & 3 deletions common/app/common/controllers/SunriseController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import common.contexts.UserContext;
import common.i18n.I18nResolver;
import common.models.LocationSelector;
import common.models.MiniCart;
import common.models.NavMenuData;
import common.templates.TemplateService;
import common.utils.PriceFormatter;
Expand All @@ -24,7 +23,10 @@
import javax.annotation.Nullable;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -150,7 +152,7 @@ private static CurrencyUnit currentCurrency(final CountryCode currentCountry, fi
.map(countryCurrency -> {
final CurrencyUnit currency = Monetary.getCurrency(countryCurrency.getCurrencyCode());
return projectContext.isCurrencyAccepted(currency) ? currency : projectContext.defaultCurrency();
}).orElse(projectContext.defaultCurrency());
}).orElseGet(projectContext::defaultCurrency);
}

private static List<Locale> acceptedLocales(final String languageTag, final Http.Request request,
Expand Down
2 changes: 1 addition & 1 deletion common/app/common/templates/CustomI18nHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public CharSequence apply(final String context, final Options options) throws IO

private String resolveMessage(final Options options, final I18nIdentifier i18nIdentifier, final List<Locale> locales) {
return resolvePluralMessage(options, i18nIdentifier, locales)
.orElse(i18n.get(locales, i18nIdentifier.bundle, i18nIdentifier.key)
.orElseGet(() -> i18n.get(locales, i18nIdentifier.bundle, i18nIdentifier.key)
.orElse(null));
}

Expand Down
2 changes: 1 addition & 1 deletion common/app/common/utils/PriceUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private PriceUtils() {

public static MonetaryAmount calculateTotalPrice(final CartLike<?> cartLike) {
final Optional<TaxedPrice> taxedPriceOpt = Optional.ofNullable(cartLike.getTaxedPrice());
return taxedPriceOpt.map(TaxedPrice::getTotalGross).orElse(cartLike.getTotalPrice());
return taxedPriceOpt.map(TaxedPrice::getTotalGross).orElseGet(cartLike::getTotalPrice);
}

public static Optional<MonetaryAmount> calculateSalesTax(final CartLike<?> cartLike) {
Expand Down
2 changes: 1 addition & 1 deletion common/app/shoppingcart/CartSessionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private CartSessionUtils() {
public static MiniCart getMiniCart(final Session session) {
return Optional.ofNullable(session.get(CartSessionKeys.MINI_CART))
.map(miniCartAsJson -> SphereJsonUtils.readObject(miniCartAsJson, MiniCart.class))
.orElse(new MiniCart());
.orElseGet(MiniCart::new);
}

public static void overwriteCartSessionData(final Cart cart, final Session session, final UserContext userContext,
Expand Down
Loading

0 comments on commit 52ac5ce

Please sign in to comment.