I wrote a request filter for geoIP localization. It works the way that I request an external service for the localization and then write the information into JCR, into a dedicated workspace for caching/storage.
On the author instance this works, but on the public instance I constantly get a AccessDeniedException. I probably need to authenticate with the JCR, and I tried that too, using the crendentials from the magnolia.properties file:
magnolia.connection.jcr.userId = username
magnolia.connection.jcr.password = password
And this code for authentication:
Session session = MgnlContext.getJCRSession(WORKSPACE_IP_ADDRESSES);
session.impersonate(new SimpleCredentials("username", "password".toCharArray()));
I have the this xml to bootstrap the filter, and a FilterOrdering Task, configured as follows:
tasks.add(new FilterOrderingTask("geoIp", new String[] { "contentType", "login", "logout", "csrfSecurity",
"range", "cache", "virtualURI" }));
What am I missing?
What would be the proper to write into the JCR in Magnolia on the public instance?
Yeah, that could not work :D
Is your filter configured in Magnolia's filter chain or directly in web.xml? It needs to live in filter chain and it needs to be configured somewhere down the chain after the security filters so that user is already authenticated.
Then you can simply call MgnlContext.getJCRSession("workspace_name") to get access to repo and do whatever you need.
HTH,
Jan
Related
I am using .AddFacebook() extension method in my Startup class. In here I set the AppId and AppSecret.
Is there any way to set these options depending on the current web request instead?
The correct way to solve the underlying problem is to specify each credentials as a separate provider.
Example:
.AddFacebook("My Facebook Provider", facebookOpts => {
facebookOpts.AppId = myAppId;
facebookOpts.AppSecret = myAppSecret;
facebookOpts.CallbackPath = "signin-facebook-myFacebookProvider";
});
It's important to override the CallbackPath! If you have multiple calls to .AddFacebook, they will all try to hit the same callback path and you will get a server error.
I already read the multi-tenancy guide published, and I believe the solution I require for my app would be to create a separate DB Connection for each organization that I sign up.
My issue is that, since I'm going to be setting the connection parameter to a different name per client, I would have liked Universal Login to automatically determine the DB Connection name according to the user's email address. So, instead of the user manually providing some kind of a hint to which DB Connection I should authenticate them against, I would like to automatically determine that somehow.
Is there any way to do this?
I am assuming that you are using hosted login page. The easiest way to determine the connection based on client would be to pass the connection parameter when redirecting to /authorize endpoint. Thus, Lock will use the connection parameter passed in the URL as the connection to validate the user. For example:
https://[tenant]/authorize?
client_id=K8B5DJdStcZtUzbhaxAOzCrXNbo2kmXG&
response_type=token%20id_token&
redirect_uri=http://application_url&
scope=openid%20profile%20email%20&
connection=connection_name&state=123&nonce=345
Both auth0.js and auth0-spa-js can be used to pass the extra parameter(connection).
Second approach would be to use connectionResolver options if you are using Hosted Login Page+ Lock .
connectionResolver {Function}: When in use, provides an extensibility point to make it possible to choose which connection to use based on the username information. Has username, context, and callback as parameters. The callback expects an object like: {type: 'database', name: 'connection name'}.
var options = {
connectionResolver: function (username, context, cb) {
var domain = username.includes('#') && username.split('#')[1];
if (domain) {
// If the username is test#auth0.com, the connection used will be the `auth0.com` connection.
// Make sure you have a database connection with the name `auth0.com`.
cb({ type: 'database', name: domain });
} else {
// Use the default approach to figure it out the connection
cb(null);
}
}
}
Instead of username, you can take advantage of context object to identify the client (context.clientID) and choose the connection.
We use Apache Shiro to authenticate and authorize users using our active directory.
Authenticating the user and mapping groups works just fine using the following config:
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.searchBase = "OU=MYORGANIZATION,DC=MYDOMAIN,DC=COM"
adRealm.groupRolesMap = "CN=SOMEREADGROUP":"read","CN=SOMEMODIFYGROUP":"modify","CN=SOMEADMINGROUP":"admin"
adRealm.url = ldaps://my.ad.url:636
adRealm.systemUsername= systemuser
adRealm.systemPassword= secret
adRealm.principalSuffix= #myorganization.mydomain.com
I can authenticate in Shiro using the following lines:
String user = "someuser";
String password = "somepassword";
Subject currentUser = SecurityUtils.getSubject ();
if (!currentUser.isAuthenticated ()){
UsernamePasswordToken token = new UsernamePasswordToken (user,
password);
token.setRememberMe (true);
currentUser.login (token);
}
We now want to get more user information from our ActiveDirectory. How can I do that using Apache Shiro? I was not able to find anything about it in the documentation.
In the source code of ActiveDirectoryRealm I found this line:
NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
So the first part of the answer is clear: use the ldapContext to search something in it. But how can I retrieve the LdapContext?
It depends on what you are trying to do. Are you just trying to reuse the context to run a query for something other then authentication or authorization? Or are you trying to change the behavior of the query in the AD realm?
If the latter, you would need to extend the ActiveDirectoryRealm and override the queryForAuthorizationInfo() method.
Are you implementing something that is custom for your environment?
(updated)
A couple things:
The realm has access to the LdapContext in the two touch points: queryForAuthenticationInfo() and queryForAuthorizationInfo(), so if you extend the AD realm or AbstractLdapRealm you should already have it. You could change the query to return other info and add the extra info to your Principal. Then you have access to that info directly from your Subject object.
Your realms, are not required to be singletons.
If you want to do some other sort of user management (email all users with a given role, create a user, etc). Then you could create a LdapContextFactory in your shiro.ini, and use the same instance for multiple objects.
[main]
...
ldapContextFactory = org.apache.shiro.realm.ldap.JndiLdapContextFactory
ldapContextFactory.systemUsername = foobar
ldapContextFactory.systemPassword = barfoo
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.ldapContextFactory = $ldapContextFactory
...
myObject = com.biz.myco.MyObject
myObject.ldapContextFactory = $ldapContextFactory
This would work well if myObject is interacting with other Shiro components, (responding to events, etc), but less so if you need access to it from another framework. You could work around this by some sort of static initialization that builds creates the ldapContextFactory, but in my opinion, this is where the sweet spot of using the shiro.ini ends, and where using Guice or Spring shines.
I'm trying the built-in Authentication of ServiceStack. My approach is 'OrmLiteAuthRepository' so users' information are stored in Sql Server instead of the default in memory storage. I use Postman to test the endpoints.
My target is receiving user rows, updating user information, creating users, deleting an user row. Those are the endpoints I found in Postman after importing (I didn't create those endpoints):
GET 'http://localhost:47391/api/register',
PUT 'http://localhost:47391/api/json/reply/Register'
POST 'http://localhost:47391/api/json/reply/Register'
I tested POST, Sql Server automatically created the tables to store user data. And the data could be written into Sql Server so I have no problem with POST.
But with PUT, isn't it for updating the existing row? I append '/{id}' to the end. But it created a new row in the database instead of updating the existing one. How does it work?
With GET, I got no implementation error.
{
"ResponseStatus": {
"ErrorCode": "NotImplementedException",
"Message": "Could not find method named Get(Register) or Any(Register) on Service RegisterService",
"StackTrace": " at ServiceStack.Host.ServiceExec`1.Execute(IRequest request, Object instance, Object requestDto, String requestName)\r\n at ServiceStack.Host.ServiceRequestExec`2.Execute(IRequest requestContext, Object instance, Object request)\r\n at ServiceStack.Host.ServiceController.<>c__DisplayClass11.<>c__DisplayClass13.<RegisterServiceExecutor>b__10(IRequest reqCtx, Object req)\r\n at ServiceStack.Host.ServiceController.ManagedServiceExec(ServiceExecFn serviceExec, IService service, IRequest request, Object requestDto)\r\n at ServiceStack.Host.ServiceController.<>c__DisplayClass11.<RegisterServiceExecutor>b__f(IRequest requestContext, Object dto)\r\n at ServiceStack.Host.ServiceController.Execute(Object requestDto, IRequest req)\r\n at ServiceStack.HostContext.ExecuteService(Object request, IRequest httpReq)\r\n at ServiceStack.Host.RestHandler.GetResponse(IRequest request, Object requestDto)\r\n at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest httpReq, IResponse httpRes, String operationName)"
}
}
How to implement it? I assume I consider the user a normal Web Service entity? and create 'UserService', and requests like:
[Route("/register")]
public class User : IReturn<UserResponse>
{
...
}
BUT there isn't a model class like 'User' due to the tables are created by ServiceStack itself, how to solve this?
Or is there something I am not aware of. Thanks.
The error message:
Could not find method named Get(Register) or Any(Register) on Service RegisterService
Is saying you're trying to call the built-in ServiceStack Register Service instead of your Service. But the Register Services isn't enabled by default, your AuthFeature likely explicitly enables it, either with:
Plugins.Add(new RegistrationFeature());
Or on the AuthFeature:
Plugins.Add(new AuthFeature(...) {
IncludeRegistrationService = true
});
If you don't want to enable ServiceStack's built-in Register Service you'll need to remove the registration where it's enabled.
If you instead want the Register Service registered at a different path, you can specify a different route with:
Plugins.Add(new RegistrationFeature {
AtRestPath = "/servicestack-register"
});
I've spend hours installing a custom login service in embedded Jetty 9.1.0.v20131115 and RESTEasy 3.0.5.Final. My login service will look users up in a database and assign them roles. It looks something like this:
final Constraint restConstraint = new Constraint();
restConstraint.setName(Constraint.__BASIC_AUTH);
restConstraint.setRoles(new String[]{"user", "admin");
restConstraint.setAuthenticate(true);
final ConstraintMapping restConstraintMapping = new ConstraintMapping();
restConstraintMapping.setConstraint(restConstraint);
restConstraintMapping.setPathSpec("/api/*");
final ConstraintSecurityHandler restSecurityHandler = new ConstraintSecurityHandler();
final LoginService myLoginService = new MyLoginService();
restSecurityHandler.setAuthenticator(new BasicAuthenticator());
restSecurityHandler.setRealmName(myLoginService.getName());
restSecurityHandler.addConstraintMapping(restConstraintMapping);
restSecurityHandler.setLoginService(myLoginService);
I have users joe-user who has the role of user, and jane-admin who has both user and admin roles. I have a REST GET resource named my-resource marked with:
#RolesAllowed("admin")
When I do an HTTP GET on my-resource, the browser correctly requests credentials, and I can login as either joe-user or jane-admin. The problem is that either user is allowed to GET my-resource!!
I've traced through some of the Jetty code, and indeed, as a result of my login service above, Jetty asks the login user which roles is supported. Unfortunately, Jetty will accept any of the roles I've specified in restConstraint.setRoles(new String[]{"user", "admin"), regardless of the user.
Apparently it is the RESTEasy layer that is supposed to recognize the #RolesAllowed("admin") annotation and validate the user. But how do I get RESTEasy to do that?
With a little help from the RESTEasy documentation, I found out that in order for RESTEasy to honor the #RolesAllowed annotations, one must turn on the resteasy.role.based.security context parameter switch in the web.xml file; or programatically, as I am doing:
final ServletHolder servletHolder = new ServletHolder(new HttpServlet30Dispatcher());
servletHolder.setInitParameter("javax.ws.rs.Application", MyApplication.class.getName());
servletHolder.setInitParameter("resteasy.role.based.security", String.valueOf(true));
contextHandler.addServlet(servletHolder, "/api/*");