Need to Pass the 'value' to my #CustomPreAuthorize annotation and use it in #PreAuthorize("hasAuthority(#myservice.check(#value))") - spring-security

Currently I am writing my own custom #PreAuthorize annotation.
My case is as follows,
I am running my authorization KeyCloak server that holds the user details, roles and permission
After Validation, I have stored the permission details in GrantedAuthority as follows "{rsname}:GET", "{rsname}:POST" ...
KeyCloak JWT permission structure:
"authorization": {
"permissions": [
{
"scopes": [
"GET",
"DELETE",
"POST"
],
"rsid": "6ae9895f-3766-464f-82c4-44f598ec2a93",
"rsname": "record"
}
]
}
while using #PreAuthorize annotation in controller instead of hardcoding the resource name and scopes, we have to generalize it by getting the details from application.property we have achieved it as follows,
application.property:
auth:
data:
name1: record
name2: device
Property Detail Component class:
#ConfigurationProperties(prefix = "auth")
#Component
public class SecurityProperty {
private Map<String, String> data;
....
}
Controller:
#RequestMapping (method = RequestMethod.GET,value = "/api/records",
produces = {"application/json"})
#PreAuthorize ("hasAuthority (#securityProperty.getData(). get('name1') "
+ "+ ': GET')")
ResponseEntity<List<SomeDTO>> getRecords() {
...Some Logic
}
#RequestMapping(method = RequestMethod.GET,value = "/api/devices",
produces = { "application/json" })
#PreAuthorize("hasAuthority(#securityProperty.getResources().get('name2') "
+ "+ ':GET')")
ResponseEntity<List<SomeDTO>> getDevices() {
...Some Logic
}
So far this is working fine. Since we are creating big project we don't want to write this lengthy #PreAuthorize(XXXX) annotation so decided to create custom annotation that uses the #PreAuthorize.
We have created #CustomPreAuthorize as below
#Retention(RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
#PreAuthorize("hasAuthority(#securityProperty.getResources().get(#resource)"
+ ".concat(':GET'))")
public #interface CustomPreAuthorize {
String resource();
}
And used this in controller
#RequestMapping (method = RequestMethod.GET,value = "/api/devices",
produces = {"application/json"})
#CustomPreAuthorize (resource = "name2")
ResponseEntity<List<SomeDTO>> getDevices() {
...Some Logic
}
Issue:
When I used like this when the API is called I am getting the following error
Failed to evaluate expression 'hasAuthority(#securityProperty.getResources().get(#resource).concat(':GET'))"
So far what I understood is like the resource and scope are not getting recognized in the #PreAuthorize annotation level. Is it possible to read the values like this or is there any alternatives available?

Since there is no reply yet for the required fix, for now we have fixed this by adding the Aspect for the annotation and proceed with manual authority check using the SecurityContextHolder.
#Before("#annotation(CustomPreAuthorize)")
void annotationPointcut() {
//business logic
}
We will check for the other solutions actively and post if we achieve it in more better way.

Related

.NET Core 2: Force authentication handler to run before middleware?

Ripping api endpoints out of a .NET Core 1 web project to a .NET Core 2 api-only project. My experience with auth (both -orization and -entication) is minimal at best, mainly because most projects I've worked on have already been setup for auth and/or it's been an AD environment.
The API portion of the site uses a pre-shared token to be included in the header of every request. This token is the key to all auth, user identification, permissions, etc etc. The user info (ie. who are you and what can you do) is contained in a custom CurrentContext class.
The Core 1 project uses middleware (ContextMiddleware) to init the CurrentContext instance that is registered in DI as scoped. By the time the ContextMiddleware class is called, the custom auth handler has already been called, the necessary header token has already been examined, authentication checks have passed, and a principal has been created. Thus, the ContextMiddleware class, which heavily depends on the principal existing, can build the CurrentContext and the truckload of information needed to know who's calling.
The Core 2 project ends up running ContextMiddleware before the authentication handler, and I can't figure out how to force the order of those two to swap.
Relevant code snippets:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// ...
// https://geeklearning.io/how-to-migrate-your-authentication-middleware-to-asp-net-core-2-0/
services.AddAuthentication( options =>
{
options.DefaultScheme = UserTokenAuthenticationDefaults.AuthenticationScheme;
} ).AddUserTokenAuthentication(UserTokenAuthenticationDefaults.AuthenticationScheme,
UserTokenAuthenticationDefaults.AuthenticationScheme,
o => { } );
// ...
}
public void Configure(IApplicationBuilder app /*...*/) {
if (env.IsDevelopment()){
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler("/error/500");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMiddleware<ContextMiddleware>();
app.UseMvc();
}
}
If more code snippets are needed for further information, please let me know. How do I force my auth handler's HandleAuthenticateAsync() to run before ContextMiddleware is invoked?
I was dealing with this too and we found it best to support a "dynamic" authentication scheme we made up that then allows a selector to be called to make the authentication type decision.
Add a DynamicAuthenticationDefaults.AuthenticationScheme = "Dynamic"; constant somewhere in your codebase per ASP.NET Core standards, then in your startup class' ConfigureServices add the dynamic scheme:
.AddAuthentication( options =>
{
options.DefaultScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
} )
.AddYourCustomAuthentication( YourCustomAuthenticationDefaults.AuthenticationScheme, YourCustomAuthenticationDefaults.AuthenticationScheme, options => { } )
.AddPolicyScheme( DynamicAuthenticationDefaults.AuthenticationScheme, DynamicAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ForwardDefaultSelector = DynamicAuthenticationSchemaSelector.Evaluate;
} );
then in a custom DynamicAuthenticationSchemaSelector class implement that evaluation method:
public static class DynamicAuthenticationSchemaSelector
{
public static string Evaluate( HttpContext context )
{
string result;
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
if( !string.IsNullOrEmpty( authHeader ) && authHeader.StartsWith( YourCustomAuthenticationDefaults.AuthenticationScheme ) )
{
result = YourCustomAuthenticationDefaults.AuthenticationScheme;
}
else
{
result = IdentityConstants.ApplicationScheme;
}
return result;
}
}
You'll get the proper authentication middleware handling.
You don't need to call this "dynamic" either and it's just for following best practices / patterns -- any string will suffice.

BreezeJs: Error: Unable to convert this endpoint to an IQueryable

I have asp.net core 1.1.0 project and trying the model of CodeCamp sample. In this we a controller which return Lookup data as below :
[BreezeController]
public class BreezeController : ApiController
{
[HttpGet]
public object Lookups()
{
var rooms = _repository.Rooms;
var tracks = _repository.Tracks;
var timeslots = _repository.TimeSlots;
return new { rooms, tracks, timeslots };
}
And the above Lookups is called in dataContext.js as below:
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.to$q(querySucceeded, _queryFailed);
function querySucceeded(data) {
log('Retrieved [Lookups]', data, true);
return true;
}
}
Now, I am trying to follow same as above in my project its giving me error as below :
Get http://Localhost:12345//breeze/demo/Lookups 500(Internal server error)
Uncaught (in promise)
Error: Unable to convert this endpoint to an IQueryable
Any solution to above issue...its working fine in John Papa's Code camper project. My web api lookups code is working fine if I run it in browser but not with breezejs.
Breeze's .NET Core implementation expects a hidden first parameter. It uses this to perform the IQueryable filtering of a REST operation. For example, if you have an operation that looks like this:
[HttpGet]
public IQueryable<Order> Orders()
And you wanted to get all Orders with the Status of 123 that Cost less than $10 then the first parameter would be something like this:
{
"where":{
"Status":123,
"Cost":{
"lt":10
}
},
"select":[
"OrderId"
]
}
This is a significant departure from the previous version. The client can be changed to pass parameters compatable with this by adding:
breeze.config.initializeAdapterInstance("uriBuilder", "json");
I added this to my fetchMetadata call.
However, this causes a lot of problems if you have specific get methods with parameters and you want to call it from Swagger or another application.
Something Like this:
[HttpGet]
public IQueryable<Order> GetOrdersByStatusAndLessThanCost(int status, int cost)
Will generate a url like this:
GetOrdersByStatusAndLessThanCost?status=123&cost=10
Breeze assumes that the first parameter (status=123) is its JSON. So it tries to parse it out.
This gives the first most common error with migrating Breeze to .NET Core:
This EntityQuery ctor requires a valid json string. The following is not json: status=123
If you happened to pass in Json, but the result is not an IQueryable, then you will get this error:
Unable to convert this endpoint to an IQueryable
The key to all of this is to give breeze what it is looking for. For the example above the following URL would work:
GetOrdersByStatusAndLessThanCost?{}&status=123&cost=10
Note the added {}& as the first parameter. This tells breeze that there is not anything expected as far as filtering goes.
To get this working for Swashbuckle (and by extension Swagger\Open API) add this to your Startup.cs ConfigureServices method inside the call to services.AddSwaggerGen(c =>:
c.OperationFilter<AddBreezeParameter>();
And then create the file that is needed for that:
public class AddBreezeParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.MethodInfo.ReturnType.Name.StartsWith("IQueryable"))
{
if (operation.Parameters == null)
{
operation.Parameters = new List<OpenApiParameter>();
}
var exampleString = "<br>\":{}," +
"<br> \"where\":{" +
"<br> \"Status\":123," +
"<br> \"Cost\":{" +
"<br> \"lt\":10" +
"<br> }" +
"<br> }," +
"<br> \"select\":[" +
"<br> \"OrderId\"," +
"<br> \"OrderDateTime\"" +
"<br> ]" +
"<br>}";
var breezeJsonParam = new OpenApiParameter
{
Name = "{\"breezeJson",
In = ParameterLocation.Query,
AllowEmptyValue = true,
Description =
"Json used to query a REST resource. <br>Due to Breeze's nonstandardness and Swashbuckle's not able to customize to allow for it, this MUST start with \":{} and end with } In between those you can put your query if it is appropriate. If you do you must add a comma after the starting value and before you value. Here is an example: " +
exampleString,
AllowReserved = true
};
var schema = new OpenApiSchema {Type = "json", Default = new OpenApiString("\":{}}")};
breezeJsonParam.Schema = schema;
operation.Parameters.Insert(0, breezeJsonParam);
}
else
{
if (operation.Parameters == null)
{
operation.Parameters = new List<OpenApiParameter>();
}
var breezeJsonParam = new OpenApiParameter();
// Breeze looks for the first parameter so it can do an IQueryable Filter on it.
// We want it to not have anything for that parameter if it is not an IQueryable.
breezeJsonParam.Name = "{}&";
breezeJsonParam.In = ParameterLocation.Query;
breezeJsonParam.Description = "Do NOT modify this parameter. (It is here for Breeze compatibility.)";
var schema = new OpenApiSchema {Example = new OpenApiString(" ")};
//var schema = new OpenApiSchema {Type = "string", Default = new OpenApiString("\":{}}")};
breezeJsonParam.Schema = schema;
operation.Parameters.Insert(0, breezeJsonParam);
}
}
}

Better way to handle common headers and root?

Is there a better way to set bearer like a global config rather than setting it each time like this:
restClient.setBearerAuth(TokenStore.getInstance().getLocalToken());
The same for root url, is there a global config rather than setting it like this:
String root= Application.getInstance().getApplicationContext().getResources().getString(R.string.whiteLabelApiBaseHost)
restClient.setRootUrl(root);
In retrofit, there is something like this:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Application.getInstance().getApplicationContext()
.getResources().getString(R.string.whiteLabelApiBaseHost))
Any idea?
To set root url you can use this method, substituting the string with a constant
#Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class }, interceptors = MyAuthInterceptor.class)
public interface MyRestClient {
#Get("/events")
EventList getEvents();
}
Note that we set an interceptor in the arguments of the #Rest annotation.
So create a class like this:
#EBean(scope = Scope.Singleton)
public class MyAuthInterceptor implements ClientHttpRequestInterceptor {
#Bean
MyAuthStore authStore;
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
HttpAuthentication auth = new HttpBasicAuthentication(authStore.getUsername(), authStore.getPassword());
headers.setAuthorization(auth);
return execution.execute(request, body);
}
}
Now before executing request MyAuthInterceptor.intercept() is called and you can set your authentication data as you prefer
In your main build.gradle file you can add inside android element
productFlavors {
development {
buildConfigField "String", "SERVICE_URL_BASE", "\"dev.xxx.com/rest\""
}
test {
buildConfigField "String", "SERVICE_URL_BASE", "\"test.xxx.com/rest\""
}
production {
buildConfigField "String", "SERVICE_URL_BASE", "\"production.xxx.com/rest\""
}
}
Then in your #Rest annotation you can use this code to get current flavor value:
#Rest(rootUrl = "https://" + BuildConfig.SERVICE_URL_BASE)
Now you can select what build variant to use (variant = flavor + buildType) to use desired value. To select variant you can use corresponding view, it should be present on the left of android studio.
This technique is useful to avoid creating flavor's package tree only to use different variabiles

Putting an object on a Request in a Grails Integration Test

In a Grails project I am looking at here, a filter puts a Domain object on the request...
class TokenFilters {
def filters = {
all( uri: '/hiphop/**' ) {
before = {
MyToken myToken = ...
request.myToken = myToken
MyToken looks like:
class MyToken {
String id
String token
static mapping = {
token( index: true )
id( generator: 'uuid' )
}
...
}
In my controller, the myToken is pulled off the request.
MyController {
myaction {
MyToken accessToken = request.myToken
All fine. I wish to write an integration test for the controller.
#Test
void testLogin() {
def mc = new MyController()
def myToken = new MyToken(1234);
// set the request parameters
mc.request.parameters = [myToken:myToken];
def message = mc.action();
assertTrue(message.indexOf("trans") > 0)
}
When I run this, I get:
Failure: testLogin(MyTests)
| java.lang.IllegalArgumentException: Parameter map value must be single value or array of type [java.lang.String]
at testLogin(MyTests.groovy:40)
So it looks like Grails will only let me a String or a single value and doesn't like me putting an object on the request in the Filter. Even thou it lets me put on the same object type in a Filter.
I'd really like to test this without going to Functional tests. Please help. I am using Grails 2.2.1
Thanks
The problem is that your code is passing parameters to the controller. Your emulating an HTTP request which can't handle objects. What you can do is:
mc.request.parameters = [myToken: '1234']
and then you're controller/filter would pull out the 1234 and look up MyToken. If you were testing the controller forwarding then you can put objects in the request. Not the other way around.
I see now that part of the problem is that you're trying to test a controller that is assuming data coming from a filter.
You've omitted some code, but assuming you are extending ControllerUnitTestCase then you have access to a mock request object. You should be able to simply do:
#Test
void testLogin() {
def mc = new MyController()
def myToken = new MyToken(1234);
// set the request parameters
request.myToken = myToken
def message = mc.action();
assertTrue(message.indexOf("trans") > 0)
}

Keep users in Config.groovy list in Grails

Is there any way to define the users that can use my application in a list in Config.groovy? This will be using Grails 2.2.3 and the latest versions of Spring Security Core and Spring Security LDAP.
We use Active Directory for authentication, and only 2 or 3 people will use this little application, so it doesn't seem worthy of making an AD Group for just this app. It would be simpler to define a list, and any time there is a new hire instead of adding them to the AD group all I have to do is add their name to the external Grails config.
I would like to do something like the following:
SomeController.groovy
#Secured("authentication.name in grailsApplication.config.my.app.usersList")
class SomeController {
}
Then in Config.groovy put this code:
my.app.usersList = ['Bill', 'Tom', 'Rick']
Is this possible? If so, is this a terrible idea? Thanks a lot.
That seems really silly. Why not have the list of users in a table? Then you can add/remove from that table without have to modify the application.
I currently do this and in my UserDetailsContextMapper I make sure the username already exists in the Users table.
You need a custom authenticator that will try to access your Active Directory and if authenticated, will look into Grails properties to check if the username is allowed to login.
This is the class that I use. I changed the code to validate the config:
class ActiveDirectoryAuthenticator {
private DefaultSpringSecurityContextSource contextFactory
private String principalSuffix = ""
def grailsApplication
public DirContextOperations authenticate(Authentication authentication) {
// Grab the username and password out of the authentication object.
String principal = authentication.getName() + "#" + principalSuffix
String password = ""
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString()
}
// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
try {
String provider = contextFactory.getUrls()[0]
Hashtable authEnv = new Hashtable(11)
authEnv.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory")
authEnv.put(Context.PROVIDER_URL, provider)
authEnv.put(Context.SECURITY_AUTHENTICATION, "simple")
authEnv.put(Context.SECURITY_PRINCIPAL, principal)
authEnv.put(Context.SECURITY_CREDENTIALS, password)
javax.naming.directory.DirContext authContext = new InitialDirContext(authEnv)
//here validate the user against your config.
if(!authentication.getName() in grailsApplication.config.adUsersAllowed) {
throw new BadCredentialsException("User not allowed.")
}
DirContextOperations authAdapter = new DirContextAdapter()
authAdapter.addAttributeValue("ldapContext", authContext)
return authAdapter
} catch ( NamingException ex ) {
throw new BadCredentialsException(ex.message)
}
} else {
throw new BadCredentialsException("Incorrect username or password")
}
}
public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* #param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory
}
public String getPrincipalSuffix() {
return principalSuffix
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* #param principalPrefix
*/
public void setPrincipalSuffix(String principalSuffix) {
if (principalSuffix != null) {
this.principalSuffix = principalSuffix
} else {
this.principalSuffix = ""
}
}
}
Declare it as your ldapAuthenticator in resources.groovy:
ldapAuthenticator(ActiveDirectoryAuthenticator) {
contextFactory = ref('contextSource')
principalSuffix = 'domain.local' //your domain suffix
grailsApplication = ref('grailsApplication')
}
The downside is that you need to restart your context when you change config.groovy
In your controllers just use #Secured('IS_AUTHENTICATED_FULLY')
I do not think you can do that because annotations are resolved at compile time and not in runtime. Config properties will be read during the application runtime so you I fear you have to end up doing:
#Secured(["authentication.name in ['Bill', 'Tom', 'Rick']"])
class SomeController {
}
If I remember correctly the #Secured annotation cannot be used for other things than comparing roles. But you should be able to do this with spring securities #PreAuthorize and #PostAuthorize annotations. When using grails the easiest way to setup these annotations is installing the spring security ACL plugin.
Within #PreAuthorize and #PostAuthorize you can use SPEL expressions which are more flexible. Unfortunatelly SPEL does not provide an in operator. However you can delegate the security check to a service:
#PreAuthorize('#securityService.canAccess(authentication)')
public void test() {
println "test?"
}
With the # symbol you can reference other beans like services within expression. Here the method securityService.canAccess() is called to evaluate if the logged in user can access this method.
To use this you have to configure a BeanResolver. I wrote some more details about configuring a BeanResolver here.
Within securityService you can now do:
class SecurityService {
def grailsApplication
public boolean canAccess(Authentication auth) {
return grailsApplication.config.myList.contains(auth.name)
}
}
In general I would not recommend to use a configuration value for validating the user in security checks. The groovy configuration will be compiled so you cannot easily add a new user without redeploying your application.

Resources