How to do role-based authorization with Apache Shiro depending on HTTP request method - rest

I'm struggling to figure out how I can do role-based authorization depending on what HTTP method a request is using. I use HTTP basic auth and depending on the users role and the HTTP method used a request should succeed or fail.
Example:
a GET request to http://localhost/rest/ should always be allowed, even to non-authenticated users (anon access)
a PUT request to http://localhost/rest/ (same resource!) should only be allowed if user is authenticated
a DELETE request to http://localhost/rest/ (same resource!) should only be allowed if user is authenticated and has the role ADMINISTRATOR
My current (non-working) attempt of configuring shiro.ini looks like this:
/rest = authcBasic[PUT], roles[SERVICE_PROVIDER]
/rest = authcBasic[POST], roles[EXPERIMENTER]
/rest = authcBasic[DELETE], roles[ADMINISTRATOR]
/rest = authcBasic
Update
I've just found https://issues.apache.org/jira/browse/SHIRO-107 and updated my shiro.ini to be
/rest/**:put = authcBasic, roles[SERVICE_PROVIDER]
/rest/**:post = authcBasic, roles[EXPERIMENTER]
/rest/**:delete = authcBasic, roles[ADMINISTRATOR]
/rest/** = authcBasic
but it still doesn't work. It seems that only the last rule matches. Also, the commit comment also seems to indicate that this only works with permission-based authorization. Is there no equivalent implementation for role-based authz?

I think HttpMethodPermissionFilter is the one you need to configure: http://shiro.apache.org/static/1.2.2/apidocs/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.html This should enable you to map the HTTP method to Shiro's "create,read,update,delete" permissions as outlined in the javadoc for the class.

I had a similar situation with Shiro and my REST application. While there may be a better way (I hadn't seen SHIRO-107), my solution was to create a custom filter extending the Authc filter (org.apache.shiro.web.filter.authc.FormAuthenticationFilter). You could do something similar extending the authcBasic filter or the Roles filter (although I think authcBasic would be better as it is probably more complicated).
The method you want to override is "protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)". Your argument (e.g. "ADMINISTRATOR") will come in on the mappedValue as String[] where the arguments were split by commas.
Since I needed the possibility of both a method and a role, I ended up have my arguements looks like "-". For example:
/rest/** = customFilter[DELETE-ADMINISTRATOR]
That let me split out the role required to perform a delete from the role required for a POST by doing something like:
/rest/** = customFilter[DELETE-ADMINISTRATOR,POST-EXPERIMENTER]
I think if you play with this, you'll be able to get the functionality you need.
BTW, I hadn't seen SHIRO-107, so I've not tried that technique and probably won't since I've already invented my own custom filter. However that may provide a cleaner solution than what I did.
Hope that helps!

Related

Generate unpredictable/unforgeable URL from predictable ID

I have a simple API that return Something for a given ID and it must be used without any kind of authentication, the URL should be permanent and yet I want to avoid as much as possible it to be botted.
The Url is something like this:
https://url/{SomeId}/doSomething
The problem is that this is very predicable and a bot could easily try all the ID and get everything associated to it.
I'm looking for a way to make the URL non predictable like for example:
https://url/{SomeId}/doSomething?Key=SomeVeryLongCryptographicKey
In this way except if you run a supercalculator you shouldn't be able to deduce all the URLs
I know that there is probably a lot of ways to do that, like using a DB which I want to avoid.
I guess I'm looking for a kind a JWT associated to the ID without expiration but maybe there is better solution.
Edit: to give a better example i'm looking to do a bit like did Zoom for permanent invitation Links. They had predictable room Ids and they added a password making it unpredictable lie so:
https://us05web.zoom.us/j/132465789?pwd=SUxIU0pLankyhTRtWmlGbFlNZ21Ndz08
What would be the best/light/"secure" way to achieve that ?
Also I'm using .Net if there is a library doing that, it would be great.
I think your idea of using a JWT makes the most sense. Better to use something standard from a cryptographic point of view, and the JSON format allows for encoding whatever you need to provide to the receiving endpoint (user names, entity names, entity IDs, other things).
There are standard Microsoft libraries for building and validating JWTs, but I prefer the library Jwt.Net (https://www.nuget.org/packages/JWT). It lets you do something like this quite easily:
var token = JwtBuilder()
.WithAlgorithm(new RS256Algorithm(publicKey,privateKey))
.AddClaim("uri", String.Format("https://example.com/api/{0}/{1}", entityName, entityId))
.Encode();
Just add whatever claims you like, and the JWT will then contain what you want to transfer (I've used an example of the URI that you want to give to the entity) and a signature with your private key. You could even just give a URL like https://example.com/from_token/eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2FwaS90ZXN0RW50aXR5LzEyMzQifQ.K2P4wSF6g1Kt-IHMzlklWTV09_MIkoiNHQztSIYOohmOWe7aBfFSQLIKSbdTECj9XPjNNG_AjH9fdjFglkPuYfr2G9rtl2eh5vTjwdM-Uc0X6RkBu0Z2j7KyMKjkaI3zfkIwhtL1mH873xEBtNOGOW18fuBpgnm8zhMAj1oD3PlDW8-fYBrfLb6VK97DGh_DyxapbksgUeHst7cAGg3Nz97InDPtYcWDi6lDuVQsj3t4iaJBRL8IM785Q8xjlHHhzdfcX3xU4IhflyNHHXxP56_8ahNNMOZKWdwgbTSIxEEB98b3naY3XknC-ea7Nc1y4_5fszrYdy3LaQWV43jpaA
and have the handler decode the entity name and ID you want to retrieve directly from the URI while verifying the signature. Decoding with the library is just as easy:
var json = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(_key))
.MustVerifySignature()
.Decode(token);

Limit token scope server-side

My login procedure allows admins to select an account that they would like to login-as. For that I can login as that particular user and issue the authorization code, as usual.
Now, what I would like is to extend this setup to allow some other admins to login with "read-only" access. This can easily be mapped to our API by use of certain scopes and removing some other scope.
For the oauth process to work, I do need a way to issue oauth tokens that come with a scope that has been limited server side (less scope than the actual client - server-side because read-only is enforced).
I imagine that I might need to write a new GrantType and probably also have to track state somehow, but I am unclear on how exactly I should use create_authorization_response() in this case.
Ok, after some fiddling around, I found a solution. It essentially creates a custom Oauth2Request (usually client-provided, in our case, modified server-side).
Some rough outline of the code:
from urllib.parse import urlencode, parse_qs, urlparse
# obtain query string as dictionary
query_dict = parse_qs(request.query_string.decode("utf-8"))
# customize scope for this request
query_dict["scope"] = ["profile"]
# We here setup a custom Oauth2Request as we have change the scope in
# the query_dict
req = OAuth2Request(
"POST", request.base_url + "?" + urlencode(query_dict, doseq=True)
)
return authorization.create_authorization_response(grant_user=user, request=req)

Request in IAuthorizationPolicy

I'm trying to implement a custom IAuthorizationPolicy in Kinto. The documentation points to https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial/authorization.html, which does not make me entirely understand how to add my IAuthorizationPolicy to the Kinto app.
My solution is to make it into a plugin, and implement the includeme function like this:
def includeme(config):
custom_authorization_policy = CustomAuthorizationPolicy()
config.set_authorization_policy(custom_authorization_policy)
But later, in IAuthorizationPolicy#permits, I would like to access the request that is currently being processed. This is because I want to cache the authentication tokens, and, as I understand it, the cache can be accessed from the request.
However, the IAuthorizationPolicy#permits takes the context parameter, and on it I can't find any request or cache.
The cache, if supported, can be accessed on config.registry.cache aswell, so I'm injecting it into my Auth policy:
def includeme(config):
custom_authorization_policy = CustomAuthorizationPolicy(config.registry.cache)
config.set_authorization_policy(custom_authorization_policy)

Correct configuration for REST endpoints in Shiro

My original post is here
I am trying to protect a set of REST endpoints with Shiro. My theory is that if I pass a JWT with my REST request, that I can use Shiro (via annotations) to secure my endpoints.
I've create my endpoints like this (for example):
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("status/{companyId}")
#RequiresAuthentication
#RequiresRoles("SomeRole")
public Response getStatus(#PathParam("companyId") int companyId){
... do stuff ...
}
I'm expecting that if I call the endpoint without authenticating, I will get a HTTP 401 error. However, the method is called successfully if the JWT is not supplied as it would be when there is no security on it at all.
I assume then that my Shiro config is incorrect. Since this is strictly a 'backend' application, I have no use for the Shiro/Stormpath configurations that apply to anything 'front-end' related (such as loginURLs, etc.)
Here is my shiro.ini :
[main]
#ERRORS IF UNCOMMENTED
#cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
#securityManager.cacheManager = $cacheManager
#stormpathClient.cacheManager = $cacheManager
# NOT NEEDED?
#sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#securityManager.sessionManager = $sessionManager
#securityManager.sessionManager.sessionIdCookieEnabled = false
#securityManager.sessionManager.sessionIdUrlRewritingEnabled = false
[urls]
/** = rest
This configuration lets every request through (as described above).
If I uncomment the [main] section, I get IllegalArgumentException: Configuration error. Specified object [stormpathClient] with property [cacheManager] without first defining that object's class. Please first specify the class property first, e.g. myObject = fully_qualified_class_name and then define additional properties.
What I need to figure out is what is the correct minimum Shiro configuration for REST endpoints (and ONLY REST endpoints) so I can allow access with a JWT.
Thanks for any help.
I'm guessing the annotations are not being processed by anything at runtime. You will need to tell your JAX-RS app to process them.
I've done this with this lib in the past:
https://github.com/silb/shiro-jersey/
Specifically something like this:
https://github.com/silb/shiro-jersey/blob/master/src/main/java/org/secnod/shiro/jersey/AuthorizationFilterFeature.java
As for the second part of the problem, my only guess is Stormpath/Shiro environment is not setup correctly.
Did you put filter config in your web.xml or is all of the config loaded from the servlet fragment?

403 forbidden error PUT request with yiirestfull plugin and Backbone.JS

I am working with REST in Yii. Therefore I use yiirestful plugin and Backbone.JS. At the moment I am perfectly able to do POST and GET request throughout REST. But when I want to update a record I alway get an 403 forbidden error. I shall explain what i've tried and how:
First of all I am saving my collection in Backbone like this:
Backbone.sync('update', this.collection);
Now I don't for sure if that's going to work but the fact is that any PUT request get's an 403.
Secondly, because I am working in a module I adjusted the URLmanager rules like this:
'contentManagement/api/<controller:\w+>'=>array('contentManagement/<controller>/restList', 'verb'=>'GET'),
'api/<controller:\w+>'=>array('<controller>/restList', 'verb'=>'GET'),
'api/<controller:\w+>/<id:\w+>'=>array('<controller>/restView', 'verb'=>'GET'),
'api/<controller:\w+>/<id:\w+>/<var:\w+>'=>array('<controller>/restView', 'verb'=>'GET'),
array('contentManagement/<controller>/restCreate', 'pattern'=>'contentManagement/api/<controller:\w+>', 'verb'=>'POST'),
array('<controller>/restUpdate', 'pattern'=>'contentManagement/api/<controller:\w+>/<id:\d+>', 'verb'=>'PUT'),
array('<controller>/restUpdate', 'pattern'=>'contentManagement/api/<controller:\w+>/<id:\d+>', 'verb'=>'PUT'),
array('<controller>/restDelete', 'pattern'=>'api/<controller:\w+>/<id:\d+>', 'verb'=>'DELETE'),
array('<controller>/restCreate', 'pattern'=>'contentManagement/api/<controller:\w+>', 'verb'=>'POST'),
array('<controller>/restCreate', 'pattern'=>'contentManagement/api/<controller:\w+>/<id:\w+>', 'verb'=>'POST'),
It could be the case that this causes the problem. But I am not very digged in to this rules.. I did the same as i did by the POST request but whatever I try it still gives the 403.
Thirdly i'll provide my request information:
PUT http://pimtest.php/Yii/trackstar/contentManagement/api/SidebarWidgetsUsed/
403 Forbidden
38ms
I think a id is needed after this url but when I provide an id in the data that should be enough to let backbone recognize it's about an PUT request / update request.
I have no clue where to look further in my code.. I understand it's a complicated story so any minimal advice is appreciated!
Greetz,
You have two (identical) rules that apply to PUT, and as you say, they require an ID. Without an ID, none of the rules will match for a PUT request.
Either provide an ID, or modify the rule to not require an id, e.g.:
array('<controller>/restUpdate', 'pattern'=>'contentManagement/api/<controller:\w+>', 'verb'=>'PUT'),
In any case, as you're not using id, I'm not sure why it's in the rule to start?