How to restrict Spartacus Authentication to certain customers? - spartacus-storefront

I'm new to Spartacus storefront development but have worked in Hybris/SAP Commerce for some years,
Setting up spartacus I noticed that in the AuthenticationService, customers can log in over the REST API from any base store. The call simply goes to hybris with user e-mail/id and password, and is then authenticated to the store.
Is there possibility to restrict only users from one basestore to that specific spartacus storefront? Or groups on said user. Just something to not expose the spartacus storefront to all Customers...
Thank you

We made a RequiredRoleMatchingFilter extends AbstractUrlMatchingFilter
that checks a custom role attribute (UserGroupModel) on BaseSite level
So something like this
private static final String ROLE_PREFIX = "ROLE_";
private BaseSiteService baseSiteService;
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
final Authentication auth = getAuth();
boolean isCustomer = false;
if (hasRole(ROLE_CUSTOMERGROUP, auth) || hasRole(ROLE_CUSTOMERMANAGERGROUP, auth)) {
isCustomer = true;
}
final Optional<BaseSiteModel> currentBaseSite = Optional.ofNullable(getBaseSiteService().getCurrentBaseSite());
if (isCustomer && currentBaseSite.isPresent() && currentBaseSite.get().getRequiredRole() != null) {
final UserGroupModel requiredRole = currentBaseSite.get().getRequiredRole();
final String REQUIRED_ROLE = ROLE_PREFIX + requiredRole.getUid().toUpperCase();
if (hasRole(REQUIRED_ROLE, auth)) {
LOG.debug("Authorized as " + requiredRole.getUid());
} else {
// could not match the authorized role
throw new AccessDeniedException("Access is denied");
}
}
filterChain.doFilter(request, response);
}

Related

Eclipse Scout authentication failure - No UserAgent set on calling context

I am using Eclipse Scout 22 and I connect my Scout application to a REST server using a modified credential verifier for user authentication. I just discovered that if I try to login using any other username apart from admin, login fails, and I get the following message on the Eclipse IDE console
No UserAgent set on calling context; include default in service-request
2023-01-28 18:17:45,280 WARN [qtp1624820151-19] org.eclipse.scout.rt.shared.servicetunnel.AbstractServiceTunnel.interceptRequest(AbstractServiceTunnel.java:84) - No UserAgent set on calling context; include default in service-request - MDC[]
Here is my credential verifier
package org.eclipse.scout.apps.ygapp.shared.security;
public class RestCredentialVerifier implements ICredentialVerifier {
private static final Logger LOG = LoggerFactory.getLogger(RestCredentialVerifier.class);
#Override
public int verify(String username, char[] passwordPlainText) throws IOException {
LOG.debug("Method \"verify\" in RestCredentialVerifier. User " + username);
// Test for missing username or password
if (StringUtility.isNullOrEmpty(username) || passwordPlainText == null
|| passwordPlainText.length == 0) {
throw new VetoException(TEXTS.get("MissingUsernameOrPassword"));
}
// Test for non-conforming password
// Password MUST have between 8 to 20 characters with a minimum of one uppercase, one lowercase,
// one number, one special character and without spaces
if ((passwordPlainText.length < 8) || (passwordPlainText.length > 20)) {
throw new VetoException(TEXTS.get("ThePasswordMustHaveBetween820Characters"));
}
Subject subject = new Subject();
subject.getPrincipals().add(new SimplePrincipal("system"));
subject.setReadOnly();
RunContext runContext = RunContexts.empty().withLocale(Locale.getDefault()); // OK
// RunContext runContext = RunContexts.copyCurrent(true).withSubject(subject); // Fails
Map<String, String> result = runContext.call(new Callable<Map<String, String>>() {
#Override
public Map<String, String> call() throws Exception {
return BEANS.get(IRestAuthenticationService.class).verify(username, passwordPlainText));
}
});
LOG.debug("Leaving method \"verify\" in RestCredentialVerifier. User " + username);
if (result.containsKey("message")
&& result.get("message").equals(TEXTS.get("YouAreNowConnectedToTheServer"))) {
return AUTH_OK;
} else {
return AUTH_FAILED;
}
}
}
Thanks a lot for your kind assistance.
Cheers,
JDaniel

Create Principal in Guice Filter

I am trying to implement a custom authentication filter in Guice. I receive the token, get the username and realm from the token and then create a Principal. Now I am stuck and I don't know how to set the Principal. It would be nice if I could just set it like this request.setUserPrincipal(principal);, but obviously I can't.
How can I do this?
My doFilter method looks like this:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.length() > 0) {
String token = authorizationHeader.substring("Bearer".length()).trim();
if (token.length() > 0) {
try {
Credentials credentials = securityService.getCredentials(token);
String username = credentials.getUsername();
String realm = credentials.getRealm();
Principal principal = new HttpPrincipal(username, realm);
// request.setUserPrincipal(principal);
LOGGER.info(credentials);
} catch (Exception e) {
LOGGER.error(e);
}
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
The servlet spec section 13.10 says:
The container establishes the caller identity of a request prior to
dispatching the request to the servlet engine. The caller identity
remains unchanged throughout the processing of the request or until
the application sucessfully calls authenticate, login or logout on the
request.
That is the reason why there is no setUserPrincipal.
But there are good news. You can provide your own getUserPrincipal because you can provide your own HttpServletRequest object. Any servlet filter can do it. Look at your code, you are calling the chain method with two parameters: the request and the response. There is no need to pass the same objects that you receive.
The spec even provides you with a helper class: HttpServletRequestWrapper. You just create your own request class as a subclass of the wrapper and override any method that you want, like getUserPrincipal.

Basic Authentication Middleware with OWIN and ASP.NET WEB API

I created an ASP.NET WEB API 2.2 project. I used the Windows Identity Foundation based template for individual accounts available in visual studio see it here.
The web client (written in angularJS) uses OAUTH implementation with web browser cookies to store the token and the refresh token. We benefit from the helpful UserManager and RoleManager classes for managing users and their roles.
Everything works fine with OAUTH and the web browser client.
However, for some retro-compatibility concerns with desktop based clients I also need to support Basic authentication. Ideally, I would like the [Authorize], [Authorize(Role = "administrators")] etc. attributes to work with both OAUTH and Basic authentication scheme.
Thus, following the code from LeastPrivilege I created an OWIN BasicAuthenticationMiddleware that inherits from AuthenticationMiddleware.
I came to the following implementation. For the BasicAuthenticationMiddleWare only the Handler has changed compared to the Leastprivilege's code. Actually we use ClaimsIdentity rather than a series of Claim.
class BasicAuthenticationHandler: AuthenticationHandler<BasicAuthenticationOptions>
{
private readonly string _challenge;
public BasicAuthenticationHandler(BasicAuthenticationOptions options)
{
_challenge = "Basic realm=" + options.Realm;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var authzValue = Request.Headers.Get("Authorization");
if (string.IsNullOrEmpty(authzValue) || !authzValue.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var token = authzValue.Substring("Basic ".Length).Trim();
var claimsIdentity = await TryGetPrincipalFromBasicCredentials(token, Options.CredentialValidationFunction);
if (claimsIdentity == null)
{
return null;
}
else
{
Request.User = new ClaimsPrincipal(claimsIdentity);
return new AuthenticationTicket(claimsIdentity, new AuthenticationProperties());
}
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
Response.Headers.AppendValues("WWW-Authenticate", _challenge);
}
}
return Task.FromResult<object>(null);
}
async Task<ClaimsIdentity> TryGetPrincipalFromBasicCredentials(string credentials,
BasicAuthenticationMiddleware.CredentialValidationFunction validate)
{
string pair;
try
{
pair = Encoding.UTF8.GetString(
Convert.FromBase64String(credentials));
}
catch (FormatException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
var ix = pair.IndexOf(':');
if (ix == -1)
{
return null;
}
var username = pair.Substring(0, ix);
var pw = pair.Substring(ix + 1);
return await validate(username, pw);
}
Then in Startup.Auth I declare the following delegate for validating authentication (simply checks if the user exists and if the password is right and generates the associated ClaimsIdentity)
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(DbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Func<string, string, Task<ClaimsIdentity>> validationCallback = (string userName, string password) =>
{
using (DbContext dbContext = new DbContext())
using(UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(dbContext))
using(ApplicationUserManager userManager = new ApplicationUserManager(userStore))
{
var user = userManager.FindByName(userName);
if (user == null)
{
return null;
}
bool ok = userManager.CheckPassword(user, password);
if (!ok)
{
return null;
}
ClaimsIdentity claimsIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
return Task.FromResult(claimsIdentity);
}
};
var basicAuthOptions = new BasicAuthenticationOptions("KMailWebManager", new BasicAuthenticationMiddleware.CredentialValidationFunction(validationCallback));
app.UseBasicAuthentication(basicAuthOptions);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
//If the AccessTokenExpireTimeSpan is changed, also change the ExpiresUtc in the RefreshTokenProvider.cs.
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider()
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
However, even with settings the Request.User in Handler's AuthenticationAsyncCore method the [Authorize] attribute does not work as expected: responding with error 401 unauthorized every time I try to use the Basic Authentication scheme.
Any idea on what is going wrong?
I found out the culprit, in the WebApiConfig.cs file the 'individual user' template inserted the following lines.
//// Web API configuration and services
//// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Thus we also have to register our BasicAuthenticationMiddleware
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Filters.Add(new HostAuthenticationFilter(BasicAuthenticationOptions.BasicAuthenticationType));
where BasicAuthenticationType is the constant string "Basic" that is passed to the base constructor of BasicAuthenticationOptions
public class BasicAuthenticationOptions : AuthenticationOptions
{
public const string BasicAuthenticationType = "Basic";
public BasicAuthenticationMiddleware.CredentialValidationFunction CredentialValidationFunction { get; private set; }
public BasicAuthenticationOptions( BasicAuthenticationMiddleware.CredentialValidationFunction validationFunction)
: base(BasicAuthenticationType)
{
CredentialValidationFunction = validationFunction;
}
}

Basic Auth to Receive Token in Spring Security

I am implementing a RESTful API where the user must authenticate. I want the user to POST their credentials in order to receive a JSON web token (JWT), which is then used for the remainder of the session. I have not found any good sources of information to set this up. In particular, I'm having trouble with the filter. Does anybody have any information or tutorials to help me set this up?
The people at Stormpath have quite a straightforward solution for achieving Oauth. Please take a look at Using Stormpath for API Authentication.
As a summary, your solution will look like this:
You will use the Stormpath Java SDK to easily delegate all your user-management needs.
When the user presses the login button, your front end will send the credentials securely to your backend-end through its REST API.
By the way, you can also completely delegate the login/register/logout functionality to the Servlet Plugin. Stormpath also supports Google, Facebook, LinkedIn and Github login.
Your backend will then try to authenticate the user against the Stormpath Backend and will return an access token as a result:
/**
* Authenticates via username (or email) and password and returns a new access token using the Account's ApiKey
*/
public String getAccessToken(String usernameOrEmail, String password) {
ApiKey apiKey = null;
try {
AuthenticationRequest request = new UsernamePasswordRequest(usernameOrEmail, password);
AuthenticationResult result = application.authenticateAccount(request);
Account account = result.getAccount();
ApiKeyList apiKeys = account.getApiKeys();
for (ApiKey ak : apiKeys) {
apiKey = ak;
break;
}
if (apiKey == null) {
//this account does not yet have an apiKey
apiKey = account.createApiKey();
}
} catch (ResourceException exception) {
System.out.println("Authentication Error: " + exception.getMessage());
throw exception;
}
return getAccessToken(apiKey);
}
private String getAccessToken(ApiKey apiKey) {
HttpRequest request = createOauthAuthenticationRequest(apiKey);
AccessTokenResult accessTokenResult = (AccessTokenResult) application.authenticateApiRequest(request);
return accessTokenResult.getTokenResponse().getAccessToken();
}
private HttpRequest createOauthAuthenticationRequest(ApiKey apiKey) {
try {
String credentials = apiKey.getId() + ":" + apiKey.getSecret();
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Content-Type", new String[]{"application/x-www-form-urlencoded"});
headers.put("Authorization", new String[]{"Basic " + Base64.encodeBase64String(credentials.getBytes("UTF-8"))});
Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
parameters.put("grant_type", new String[]{"client_credentials"});
HttpRequest request = HttpRequests.method(HttpMethod.POST)
.headers(headers)
.parameters(parameters)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Then, for every authenticated request, your backend will do:
/** This is your protected API */
public void sayHello(String accessToken) throws OauthAuthenticationException {
try {
if (verify(accessToken)) {
doStartEngines(); //Here you will actually call your internal doStartEngines() operation
}
} catch (OauthAuthenticationException e) {
System.out.print("[Server-side] Engines not started. accessToken could not be verified: " + e.getMessage());
throw e;
}
}
private boolean verify(String accessToken) throws OauthAuthenticationException {
HttpRequest request = createRequestForOauth2AuthenticatedOperation(accessToken);
OauthAuthenticationResult result = application.authenticateOauthRequest(request).execute();
System.out.println(result.getAccount().getEmail() + " was successfully verified");
return true;
}
private HttpRequest createRequestForOauth2AuthenticatedOperation(String token) {
try {
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Authorization", new String[]{"Bearer " + token});
HttpRequest request = HttpRequests.method(HttpMethod.GET)
.headers(headers)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
All this will not need any special Spring Security configuration, this is plain Java code that you can run in any framework.
Please take a look here for more information.
Hope that helps!
Disclaimer, I am an active Stormpath contributor.
Here's a working sample code from Spring Security OAuth github.
https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/jwt
You probably don't even need to mess with the filters as shown in the above example. If you've custom needs, please post some sample code.

Maximum threads issue

To begin with, I checked the discussions regarding this issue and couldn't find an answer to my problem and that's why I'm opening this question.
I've set up a web service using restlet 2.0.15.The implementation is only for the server. The connections to the server are made through a webpage, and therefore I didn't use ClientResource.
Most of the answers to the exhaustion of the thread pool problem suggested the inclusion of
#exhaust + #release
The process of web service can be described as a single function.Receive GET requests from the webpage, query the database, frame the results in XML and return the final representation. I used a Filter to override the beforeHandle and afterHandle.
The code for component creation code:
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8188);
component.getContext().getParameters().add("maxThreads", "512");
component.getContext().getParameters().add("minThreads", "100");
component.getContext().getParameters().add("lowThreads", "145");
component.getContext().getParameters().add("maxQueued", "100");
component.getContext().getParameters().add("maxTotalConnections", "100");
component.getContext().getParameters().add("maxIoIdleTimeMs", "100");
component.getDefaultHost().attach("/orcamento2013", new ServerApp());
component.start();
The parameters are the result of a discussion present in this forum and modification by my part in an attempt to maximize efficiency.
Coming to the Application, the code is as follows:
#Override
public synchronized Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attach("/{taxes}", ServerImpl.class);
//router.attach("/acores/{taxes}", ServerImplAcores.class);
System.out.println(router.getRoutes().size());
OriginFilter originFilter = new OriginFilter(getContext());
originFilter.setNext(router);
return originFilter;
}
I used an example Filter found in a discussion here, too. The implementation is as follows:
public OriginFilter(Context context) {
super(context);
}
#Override
protected int beforeHandle(Request request, Response response) {
if (Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE");
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
response.setEntity(new EmptyRepresentation());
return SKIP;
}
}
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
if (!Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE"); //
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}
}
super.afterHandle(request, response);
Representation requestRepresentation = request.getEntity();
if (requestRepresentation != null) {
try {
requestRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
requestRepresentation.release();
}
Representation responseRepresentation = response.getEntity();
if(responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException ex) {
Logger.getLogger(OriginFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
}
}
}
The responseRepresentation does not have a #release method because it crashes the processes giving the warning WARNING: A response with a 200 (Ok) status should have an entity (...)
The code of the ServerResource implementation is the following:
public class ServerImpl extends ServerResource {
String itemName;
#Override
protected void doInit() throws ResourceException {
this.itemName = (String) getRequest().getAttributes().get("taxes");
}
#Get("xml")
public Representation makeItWork() throws SAXException, IOException {
DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
DAL dal = new DAL();
String ip = getRequest().getCurrent().getClientInfo().getAddress();
System.out.println(itemName);
double tax = Double.parseDouble(itemName);
Document myXML = Auxiliar.getMyXML(tax, dal, ip);
myXML.normalizeDocument();
representation.setDocument(myXML);
return representation;
}
#Override
protected void doRelease() throws ResourceException {
super.doRelease();
}
}
I've tried the solutions provided in other threads but none of them seem to work. Firstly, it does not seem that the thread pool is augmented with the parameters set as the warnings state that the thread pool available is 10. As mentioned before, the increase of the maxThreads value only seems to postpone the result.
Example: INFO: Worker service tasks: 0 queued, 10 active, 17 completed, 27 scheduled.
There could be some error concerning the Restlet version, but I downloaded the stable version to verify this was not the issue.The Web Service is having around 5000 requests per day, which is not much.Note: the insertion of the #release method either in the ServerResource or OriginFilter returns error and the referred warning ("WARNING: A response with a 200 (Ok) status should have an entity (...)")
Please guide.
Thanks!
By reading this site the problem residing in the server-side that I described was resolved by upgrading the Restlet distribution to the 2.1 version.
You will need to alter some code. You should consult the respective migration guide.