How to evaluate permissions with Keycloak authzClient? - java

I have a protected resource on Keycloak created via remote API with this code:
private fun ProtectedEntity.protect(location: URI, principal: Principal) {
val newResource = ResourceRepresentation()
newResource.name = this.id
newResource.displayName = this.name
newResource.type = "urn:$appName:resources:${this.javaClass.simpleName.toLowerCase().replace("entity", "")}"
newResource.owner = ResourceOwnerRepresentation(this.creator)
newResource.ownerManagedAccess = true
newResource.uris = setOf(location.path.toString())
authzClient.protection().resource().create(newResource)
}
The resource owner is the user who invoked the method and he can manage his own resources.
Now I have to check if a user has permission to access a resource and if not, I guess I should return a ticket in the case the user would like request access to the resource. I tried with this but authorize() throws "Error creating permission ticket".
override fun read(id: ID, principal: Principal): Mono<ResponseEntity<E>> {
val currentUserToken = principal!!.getCurrentUserToken()
val resource = authzClient.protection().resource().findByName(id.toString(), currentUserToken.getUsername())
val token = currentUserToken.tokenValue
val request = AuthorizationRequest()
request.addPermission(resource.id)
request.setRpt(token)
// This returns Error creating permission ticket !?
val response = authzClient.authorization().authorize(request)
val result = authzClient.protection().introspectRequestingPartyToken(response.token)
println(result.active)
if (result.active) {
return super.read(id, principal)
} else throw RuntimeException("Result token RPT is not active!")
}
How I would delegate on Keycloak permission evaluating using authzClient?

The solution is to replace .findByName() by .findByUri(). This endpoint not take into account the resource's owner. I created a gist just in case someone else may need it: ResourceAccessKeycloakWebFilter

Related

Is it possible to use the DeviceCode authentication Flow with Azure Java SDK?

I successfully generate an IAuthenticationResult using the azure msal4jlibrary - I am presented with a device code, and when that code is typed into a browser, it shows the correct scopes / permissions,
and now I'd like to take this authentication result and pass it into the Azure-SDK authentication similar to:
val result = DeviceCodeFlow.acquireTokenDeviceCode()
val a: Azure = Azure.configure()
.withLogLevel(LogLevel.BODY_AND_HEADERS)
.authenticate(AzureCliCredentials.create(result))
.withDefaultSubscription()
Does anyone know where to look / or any samples which do this?
If you want to use msal4j library to get access token, then use the token to manage Azure resource with Azure management SDK, please refer to the following code
public class App {
public static void main(String[] args) throws Exception {
String subscriptionId = ""; // the subscription id
String domain="";// Azure AD tenant domain
DeviceCodeTokenCredentials tokencred = new DeviceCodeTokenCredentials(AzureEnvironment.AZURE,domain);
Azure azure =Azure.configure()
.withLogLevel(LogLevel.BASIC)
.authenticate(tokencred)
.withSubscription(subscriptionId);
for(AppServicePlan plan : azure.appServices().appServicePlans().list()) {
System.out.println(plan.name());
}
}
}
// define a class to extend AzureTokenCredentials
class DeviceCodeTokenCredentials extends AzureTokenCredentials{
public DeviceCodeTokenCredentials(AzureEnvironment environment, String domain) {
super(environment, domain);
}
#Override
public String getToken(String resource) throws IOException {
// use msal4j to get access token
String clientId="d8aa570a-68b3-4283-adbe-a1ad3c1dfd8d";// you Azure AD application app id
String AUTHORITY = "https://login.microsoftonline.com/common/";
Set<String> SCOPE = Collections.singleton("https://management.azure.com/user_impersonation");
PublicClientApplication pca = PublicClientApplication.builder(clientId)
.authority(AUTHORITY)
.build();
Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) ->
System.out.println(deviceCode.message());
DeviceCodeFlowParameters parameters =
DeviceCodeFlowParameters
.builder(SCOPE, deviceCodeConsumer)
.build();
IAuthenticationResult result = pca.acquireToken(parameters).join();
return result.accessToken();
}
}

Keycloak check permissions via Authzclient

i'm trying to check user permissions from a keycloak server via the keycloak authzclient. But failing constantly, by now i'm not sure if i have some misconceptions about the process.
AuthzClient authzClient = AuthzClient.create();
String eat = authzClient.obtainAccessToken("tim", "test123").getToken();
AuthorizationResource resource = authzClient.authorization(eat);
PermissionRequest request = new PermissionRequest();
request.setResourceSetName("testresource");
String ticket = authzClient.protection().permission().forResource(request).getTicket();
AuthorizationResponse authResponse = resource.authorize(new AuthorizationRequest(ticket));
System.out.println(authResponse.getRpt());
The last call authResponse.getRpt() fails with a 403 forbidden.
But the following settings in the admin console evaluates to Permit?
keycloak evaluation setting
The Client config is:
{
"realm": "testrealm",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "tv",
"credentials": {
"secret": "d0c436f7-ed19-483f-ac84-e3b73b6354f0"
},
"use-resource-role-mappings": true
}
The following code:
AuthzClient authzClient = AuthzClient.create();
String eat = authzClient.obtainAccessToken("tim", "test123").getToken();
EntitlementResponse response = authzClient.entitlement(eat).getAll("tv");
String rpt = response.getRpt();
TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);
if (requestingPartyToken.getActive()) {
for (Permission granted : requestingPartyToken.getPermissions()) {
System.out.println(granted.getResourceSetId()+" "+granted.getResourceSetName()+" "+granted.getScopes());
}
}
Just gives me the "default resource"
7d0f10d6-6f65-4866-816b-3dc5772fc465 Default Resource []
But even when i put this Default Resource in the first code snippet
...
PermissionRequest request = new PermissionRequest();
request.setResourceSetName("Default Resource");
...
it fives me a 403 . Where am I wrong?
Kind regards
Keycloak Server is 3.2.1.Final.
keycloak-authz-client is 3.2.0.Final.
Minutes after posting found the problem. Sorry. I had to perform an EntitlementRequest.
AuthzClient authzClient = AuthzClient.create();
String eat = authzClient.obtainAccessToken("tim", "test123").getToken();
PermissionRequest request = new PermissionRequest();
request.setResourceSetName("testresource");
EntitlementRequest entitlementRequest = new EntitlementRequest();
entitlementRequest.addPermission(request);
EntitlementResponse entitlementResponse = authzClient.entitlement(eat).get("tv", entitlementRequest);
String rpt = entitlementResponse.getRpt();
TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);
if (requestingPartyToken.getActive()) {
for (Permission granted : requestingPartyToken.getPermissions()) {
System.out.println(granted.getResourceSetId()+" "+granted.getResourceSetName()+" "+granted.getScopes());
}
}
ouputs:
27b3d014-b75a-4f52-a97f-dd01b923d2ef testresource []
Kind regards

"Response has already been written" with Vertx

I am brand new to Vertx and trying to create my first HTTP Rest Server. However, I have been running into this issue. Here is the error I'm getting when I try to send a response.
Jan 07, 2017 3:54:36 AM io.vertx.ext.web.impl.RoutingContextImplBase
SEVERE: Unexpected exception in route
java.lang.IllegalStateException: Response has already been written
at io.vertx.core.http.impl.HttpServerResponseImpl.checkWritten(HttpServerResponseImpl.java:559)
at io.vertx.core.http.impl.HttpServerResponseImpl.putHeader(HttpServerResponseImpl.java:156)
at io.vertx.core.http.impl.HttpServerResponseImpl.putHeader(HttpServerResponseImpl.java:54)
at com.themolecule.utils.Response.sendResponse(Response.kt:25)
at com.themolecule.api.UserAPI.addUser(UserAPI.kt:52)
at TheMoleculeAPI$main$3.handle(TheMoleculeAPI.kt:50)
at TheMoleculeAPI$main$3.handle(TheMoleculeAPI.kt:19)
at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:215)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:78)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:94)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.authorise(AuthHandlerImpl.java:86)
at
And this is how I have set up my routes.
// setup verx
val vertx = Vertx.vertx()
val router = Router.router(vertx)
val allowHeaders = HashSet<String>()
allowHeaders.add("x-requested-with")
allowHeaders.add("Access-Control-Allow-Origin")
allowHeaders.add("origin")
allowHeaders.add("Content-Type")
allowHeaders.add("accept")
val allowMethods = HashSet<HttpMethod>()
allowMethods.add(HttpMethod.GET)
allowMethods.add(HttpMethod.POST)
allowMethods.add(HttpMethod.DELETE)
allowMethods.add(HttpMethod.PATCH)
router.route().handler(CorsHandler.create("*")
.allowedHeaders(allowHeaders)
.allowedMethods(allowMethods))
router.route().handler(BodyHandler.create())
val config = JsonObject().put("keyStore", JsonObject()
.put("path", "keystore.jceks")
.put("type", "jceks")
.put("password", "password"))
// protect the API, login outside of JWT
router.route("/them/*").handler(JWTAuthHandler.create(jwt, "/them/login"))
//login routes
router.post("/them/login").handler { it -> loginAPI.login(it, jwt) }
//user routes
router.get("/them/user/getusers").handler { it -> userAPI.getUsers(it) }
router.post("/them/user/addUser").handler { it -> userAPI.addUser(it) }
This is the code that it seems to have the problem with.
fun sendResponse(responseCode: Int, responseMsg: String, context: RoutingContext) {
val userResponse = JsonObject().put("response", responseMsg)
context
.response()
.putHeader("content-type", "application/json")
.setStatusCode(responseCode)
.end(userResponse.encodePrettily())
}
Am I doing something wrong with the handlers? I tried to change the method for the response up a bunch of times, but nothing seems to work. This is written in Kotlin. If I need to add more context, just say the word!
edit: addUser method added
fun addUser(context: RoutingContext) {
val request = context.bodyAsJson
val newUser = NewUserRequestDTO(request.getString("userID").trim(), request.getString("password").trim())
newUser.userID.trim()
if (!newUser.userID.isNullOrEmpty() && !newUser.password.isNullOrEmpty()) {
//check user ID
if (userDAO.userIdInUse(newUser.userID)) {
Response.sendResponse(400, Response.ResponseMessage.ID_IN_USE.message, context)
return
}
//check password valid
val isValid: Boolean = SecurityUtils.checkPasswordCompliance(newUser.password)
if (!isValid) {
Response.sendResponse(400, Response.ResponseMessage.PASSWORD_IS_NOT_VALID.message, context)
return
}
val saltedPass = SecurityUtils.genSaltedPasswordAndSalt(newUser.password)
userDAO.addUser(newUser.userID, saltedPass.salt.toString(), saltedPass.pwdDigest.toString())
} else {
Response.sendResponse(401, Response.ResponseMessage.NO_USERNAME_OR_PASSWORD.message, context)
return
}
Response.sendResponse(200, Response.ResponseMessage.USER_CREATED_SUCCESSFULLY.message, context)
}

Oauth2 / Password flow / check permission for a specific entity

The main data information and information in my API is linked to a project(Entity),
What is good approach for password flow: to manage specific permission linked to a project with spring security and OAuth2?
In this application you have 5 micro service:
UAA microservice : the authorization server
Catalog microservice
Order microservice
Invoice microservice
Customer microservice
Zoom permission :
Each user can have many project, and can have the permission for each project:
CAN_MANAGE_CATALOG
CAN_VIEW_CATALOG
CAN_MANAGE_ORDER
CAN_VIEW_ORDER
CAN_MANAGE_INVOICE
CAN_VIEW_INVOICE
...
I have many idea but i am not sure if i have the good approach :
USE CASE : I want to securise the endpoint :
http://catalog-service/{project_key}/catalogs
Only USER who have permission VIEW_CATALOG OR MANAGE_CATALOG for the project {project_key} can list all catalog present in the project
My first idea : USE ProjectAccessExpression with preauthorize
CatalogController.java
#Controller
public class CatalogController {
#PreAuthorize("#projectAccessExpression.hasPermission(#projectKey, 'manageCatalog', principal)" +
" or #projectAccessExpression.hasPermission(#projectKey, 'viewCatalog', principal)")
#RequestMapping(
value = "/{projectKey}/catalogs",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
public #ResponseBody List<Catalog> findByProject(#PathVariable("projectKey") String projectKey) {
return catalogService.find();
}
}
ProjectAccessExpression.java
#Component
public class ProjectAccessExpression {
private RestTemplate restTemplate;
public boolean havePermission(String projectKey, String permission , String username) {
Boolean havePermission = restTemplate.getForObject(String.format("http://uaa-service/permission/check?project=%1&permission=%2&username=%3",
projectKey, permission, username
), Boolean.class);
return havePermission;
}
}
The inconvenient : is need to call UAA Service for each time
Second idea : USE USER_ROLE
With user_role
username | role
mylogin1 | SHOP1.CAN_MANAGE_CATALOG
mylogin1 | SHOP1.CAN_VIEW_CATALOG
mylogin1 | SHOP2.CAN_MANAGE_CATALOG
mylogin1 | SHOP2.CAN_VIEW_CATALOG
mylogin1 | SHOP2.CAN_MANAGE_ORDER
mylogin1 | SHOP2.CAN_VIEW_ORDER
...
SHOP1 is SHOP2 is projectKey
The inconvenient : i am not sure but if user change permission, i need to revoke all token associate
Third idea : add specific permission in authentication blob
I don't know how to do for storing...
And in controller a annotation :
#PreAuthorize("#ProjectAccessExpression.hasPermission(authentication, 'manageCatalog||viewCatalog', #projectKey)
The inconvenient : same inconvenient at second idea
It basically looks like your just trying to leverage roles with OAuth 2.0 for your project. Here is an excerpt of some spring documentation on OAuth 2.0
Mapping User Roles to Scopes: http://projects.spring.io/spring-security-oauth/docs/oauth2.html
It is sometimes useful to limit the scope of tokens not only by the scopes assigned to the client, but also according to the user's own permissions. If you use a DefaultOAuth2RequestFactory in your AuthorizationEndpoint you can set a flag checkUserScopes=true to restrict permitted scopes to only those that match the user's roles. You can also inject an OAuth2RequestFactory into the TokenEndpoint but that only works (i.e. with password grants) if you also install a TokenEndpointAuthenticationFilter - you just need to add that filter after the HTTP BasicAuthenticationFilter. Of course, you can also implement your own rules for mapping scopes to roles and install your own version of the OAuth2RequestFactory. The AuthorizationServerEndpointsConfigurer allows you to inject a custom OAuth2RequestFactory so you can use that feature to set up a factory if you use #EnableAuthorizationServer.
All this basically boils down to is that you can protect your endpoints with different scopes by mapping scopes to your own custom roles. This will allow you to get really fine grained with your security.
I found a pretty good walk-through you can use as a reference: (Obviously you'll have to configure the settings to your own use case)
https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/
This solution i use and work fine
** 1 - Load Business Logic Security when user sign **
This example find user with role persist in database, and add all role depending project. After the operation i have authentication token with
GrantedAuthority : ROLE_USER, ROLE_MANAGE_CATALOG:project1, ROLE_VIEW_PROFILE:project1, ROLE_MANAGE_PROJECT:project2, ...
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userService.findByLogin(username);
if (!user.isPresent()) {
Object args[] = {username};
throw new UsernameNotFoundException(
messageSource.getMessage("user.notexist", args, "User {0} doesn't not exist", LocaleContextHolder.getLocale())
);
}
if (!user.get().isActivated()) {
//throw new UserNotActivatedException(String.format("User %s was not activated!", username));
Object args[] = {username};
throw new UserNotActivatedException(
messageSource.getMessage("user.notactivated", args, "User {0} was not activated", LocaleContextHolder.getLocale()));
}
//Here implement your proper logic
//Add busness logic security Roles
// eg ROLE_MANAGE_PROJECT:{project_key}, ROLE_MANAGE_CATALOG:{project_key}
List<Role> bRoles = projectService.getRolesForUser(username)
user.get().getRoles().addAll(
bRoles
);
UserRepositoryUserDetails userDetails = new UserRepositoryUserDetails(user.get());
return userDetails;
}
}
** 2 check security with preauthorize expression **
In this example only user who have this permission can do this operation:
ROLE_ADMIN OR
ROLE_MANAGE_PROJECT:{projectKey}
#PreAuthorize("#oauthUserAccess.hasPermission(authentication, '"+Constants.PP_MANAGE_PROJECT+"', #projectKey)")
#RequestMapping(
value="/projects/{projectKey}",
method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity updateProject(#PathVariable("projectKey") String projectKey,#Valid #RequestBody Project project)
The OauthUserAccess class :
#Component("oauthUserAccess")
public class OauthUserAccess {
/**
* Check if it is the administrator of the application IMASTER
* #param authentication
* #param projectKey
* #return
*/
public boolean hasAdminPermission(OAuth2Authentication authentication, String projectKey) {
if(authentication.getOAuth2Request().getAuthorities().contains("ROLE_ADMIN")) return true;
return false;
}
/**
*
* #param authentication
* #param permissionType
* #param projectKey
* #return
*/
public boolean hasPermission(OAuth2Authentication authentication, String permissionType, String projectKey) {
if (!ProjectPermissionType.exist(permissionType) ||
projectKey.isEmpty() ||
!projectKey.matches(Constants.PROJECT_REGEX))
return false;
if (authentication.isClientOnly()) {
//TODO check scope permission
if(authentication.getOAuth2Request().getScope().contains(permissionType+":"+projectKey)) return true;
}
if (hasAdminPermission(authentication, projectKey)) return true;
String projectPermission = "ROLE_" + permissionType + ":" + projectKey;
String projectPermissionManage = "ROLE_" + permissionType.replace("VIEW", "MANAGE") + ":" + projectKey;
String manageProject = "ROLE_" + Constants.PP_MANAGE_PROJECT + ":" + projectKey;
Predicate<GrantedAuthority> p = r -> r.getAuthority().equals(projectPermission) || r.getAuthority().equals(projectPermissionManage) || r.getAuthority().equals(manageProject);
if (authentication.getAuthorities().stream().anyMatch(p)) {
return true;
};
return false;
}
}
3 - Advantage / disadvantage
Advantage
the business logic permission is loaded only when user login to the application and not every times so it is powerfull solution for microservice architecture.
Disadvantage
Need to update authentication token or revoke the token when permission change else
when you update the permission for user, the user required to logout and login. But you have same issue without this security logic, for example when user is disabled or enable.
My solution i use for example in a controller :
newAuthorities = projectService.getRolesForUser(username);
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), newAuthorities);
OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByUserName(principal.getName());
OAuth2Authentication auth2 = new OAuth2Authentication(authentication.getOAuth2Request(), newAuth);
accessTokens.forEach(token -> {
if (!token.isExpired()) {
tokenStore.storeAccessToken(token, auth2);
}
});

Play framework and ionic for mobile I need Security without cookies but token

I have an issue with using mobile app that I created in Ionic and back-end APIs that I coded in play framework, my issue simply is I need way to handle security matter for calling APIs that need to be secured, mean user must be logged in to do actions, as example guest can view group but can join only if logged in.
My issue that I believe that cookies is not supported for Mobile, i have code checking session that stored in cookies, its working for website, but it will not work for mobile, right?
Currently I'm trying to send a token that generated in back-end and returned with login response and stored in localStorage in Ionic, but my issue is that I can't sent token to be validated with request.
Front End:
I have the following http interceptor :
angular
.module('app.core')
.factory('sessionInjector', sessionInjector);
/** #ngInject */
function sessionInjector($q, sessionService, $rootScope) {
return {
request: function (config) {
$rootScope.$broadcast('loading:show');
if (!sessionService.isAnonymous())
config.headers['x-session-token'] = sessionService.getToken();
}
return config;
}
}
Back-End:
Controller:
#Security.Authenticated(Secure.class)
public Result joinOrganization() {
// Do some business
}
Secure.java :
#Override
public String getUsername(Http.Context ctx) {
// Is this correct way? I get null here
String str = ctx.request().getHeader("x-session-token");
String userId = ctx.session().get("usedId");
if (userId == null) {
return null;
}
User user = Play.application().injector().instanceOf(UserService.class).findUserById(Integer.parseInt(userId));
if (user != null && user.isActive) {
return user.id;
} else {
return null;
}
}
#Override
public Result onUnauthorized(Http.Context ctx) {
return unauthorized(results);
}
Note: Tokens stored in database:
Entity:
#Entity
#Table(name = "AUTHTOKEN")
public class AuthToken extends BaseModel {
#OneToOne(targetEntity = User.class, cascade = CascadeType.REFRESH, optional = false)
public User user;
#Column(nullable = false)
public String token;
#Column
public long expiration;
public AuthToken() {
}
}
For cookies working, but need to remove cookies and use tokens, or use them together cookies for website, tokens for mobile .
Mobile is no different to otherwise in regards of cookies. There are restrictions if AJAX, or cross-domain requests are used, and specially with Apple stuff, but they apply to non-mobile too. If your cookies work on a PC/Mac, they should do on a mobile device just as well. It's more of a form factor than anything else...
I found solution and it was complicated because there are many issues starting from that ngResource does not apply request interceptor its an issue opened from long time.
Second issue was how to send the token with ngResource, its simply with adding headers, the another issue here is getting dynamically the token, this "dynamically" means because the localStorage in memory getting lost when refresh so you need to get back it, this can be done with service, and function call for getting the token, something like this :
$resource('/user/:userId/card/:cardId', {userId:123, cardId:'#id'}, {
charge: {method:'POST', params:{charge:true}, headers = {
'x-session-token': function () {
return sessionService.getToken()
}}
});
Inside sessionService :
// this to recreate the cache in memory once the user refresh, it will keep the data if exisit but will point to it again in memory
if (CacheFactory.get('profileCache') == undefined) {
//if there is no cache already then create new one and assign it to profileCache
CacheFactory.createCache('profileCache');
}
function getCurrentSession() {
var profileCache = CacheFactory.get('profileCache');
if (profileCache !== undefined && profileCache.get('Token') !== undefined) {
return profileCache.get('Token');
}
return null;
}
function getToken() {
var currentSession = getCurrentSession();
if (currentSession != null && currentSession != '') {
return currentSession.token;
}
return null;
}
And then this method will work inside Secure.java
protected User getUser(Http.Context ctx) {
String token = ctx.request().getHeader("x-session-token");
if (token != null) {
return securityService.validateToken(token);
}
return null;
}

Categories