{"action":"created","issue":{"url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/1995","repository_url":"https://api.github.com/repos/jhipster/generator-jhipster","labels_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/1995/labels{/name}","comments_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/1995/comments","events_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/1995/events","html_url":"https://github.com/jhipster/generator-jhipster/issues/1995","id":105944267,"number":1995,"title":"Feature request: Dynamic Role Management","user":{"login":"deepu105","id":1107223,"avatar_url":"https://avatars.githubusercontent.com/u/1107223?v=3","gravatar_id":"","url":"https://api.github.com/users/deepu105","html_url":"https://github.com/deepu105","followers_url":"https://api.github.com/users/deepu105/followers","following_url":"https://api.github.com/users/deepu105/following{/other_user}","gists_url":"https://api.github.com/users/deepu105/gists{/gist_id}","starred_url":"https://api.github.com/users/deepu105/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deepu105/subscriptions","organizations_url":"https://api.github.com/users/deepu105/orgs","repos_url":"https://api.github.com/users/deepu105/repos","events_url":"https://api.github.com/users/deepu105/events{/privacy}","received_events_url":"https://api.github.com/users/deepu105/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/jhipster/generator-jhipster/labels/feature%20request","name":"feature request","color":"0052cc"},{"url":"https://api.github.com/repos/jhipster/generator-jhipster/labels/in-progress","name":"in-progress","color":"0e8200"},{"url":"https://api.github.com/repos/jhipster/generator-jhipster/labels/major","name":"major","color":"5319e7"},{"url":"https://api.github.com/repos/jhipster/generator-jhipster/labels/microservice","name":"microservice","color":"507889"}],"state":"open","locked":false,"assignee":{"login":"deepu105","id":1107223,"avatar_url":"https://avatars.githubusercontent.com/u/1107223?v=3","gravatar_id":"","url":"https://api.github.com/users/deepu105","html_url":"https://github.com/deepu105","followers_url":"https://api.github.com/users/deepu105/followers","following_url":"https://api.github.com/users/deepu105/following{/other_user}","gists_url":"https://api.github.com/users/deepu105/gists{/gist_id}","starred_url":"https://api.github.com/users/deepu105/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deepu105/subscriptions","organizations_url":"https://api.github.com/users/deepu105/orgs","repos_url":"https://api.github.com/users/deepu105/repos","events_url":"https://api.github.com/users/deepu105/events{/privacy}","received_events_url":"https://api.github.com/users/deepu105/received_events","type":"User","site_admin":false},"milestone":null,"comments":47,"created_at":"2015-09-11T04:45:22Z","updated_at":"2016-05-03T07:19:46Z","closed_at":null,"body":"I thought i'll wait for [User Management](https://github.com/jhipster/generator-jhipster/pull/1930) to be released before opening this, but seems like its good to start discussion as there could be some tweaks in user management based on this.\r\nThere has been already some discussion on this here (https://github.com/jhipster/generator-jhipster/issues/1525) but I would like to start a fresh thread concentrating on this\r\n\r\nIssue\r\n--------\r\n\r\nCurrently Jhipster has a role based access control implemented but it has the following short comings/limitations\r\n+ the ACL is hard coded (this is the major issue as any change in role access needs a system change which is not ideal in production)\r\n+ there is no method level or entity level security, current model provides either all or just entities based on URL\r\n+ current model is suitable only for simple use cases like a simple blog etc, its not suitable when you have lot of users with different access rights to different entities/section/modules\r\n+ current implementation is bit misleading as it actually does not do any permission check\r\n+ above all its difficult to maintain and lot of code to manage spread out here and there\r\n\r\nSolution\r\n-----------\r\n\r\nThis is a simplified version of what I have done for a customer in a JHipster app and it is working beautifully, but my use case was far more complex so I had it working in a similar fashion\r\n\r\n+ assign a unique resource name for each entity to begin with say `BOOK_RESOURCE` (users can further tweak this to have multiple section for a same entity if needed) this name can be generated during entity generation and updated in an enum or as a DB entry in a changelog at back end. The same can be put as a static variable in each Java Resource file and also updated into the route.js files\r\n+ have a role management module to create/edit roles like `ROLE_ADMIN`, `ROLE_CUSTOMER` etc\r\n+ In a role assign grant to resources, grant will be a numeric value from 0 to 4, each mapping to GET,POST,PUT and DELETE\r\n      * 0 -> no grant\r\n      * 1 -> view only\r\n      * 2 -> view & create\r\n      * 3 -> view, create & edit\r\n      * 4 -> view, create, edit & delete\r\n+ persist this resource grant mapping in DB for the role/authority and assign the role to users in user management, now we know which user has what grant to each resource\r\n+ In an `EntityResource` class have all REST methods annotated with `@Secured(RESOURCE_NAME)`\r\n+ have a `CustomAccessDecisionManager` which checks the resource against users grant\r\n+ In JS do a similar check for the routes\r\n\r\nand voila we have a completely controlled and dynamic access management\r\nthis also gives end users opportunity to further customize for their needs to obtain complex role access logic\r\n\r\nSo this is an idea, I'm definitely not a spring security expert so there might be better ways to do it, but if the dev board and community thinks that this solution is good i'll be happy to implement it in JHipster\r\n@jdubois @gzsombor @atomfrede @andidev @gmarziou \r\n\r\nIt would be great to get opinion from the spring security guys as well @rwinch I would be grateful if you can comment on this approach\r\n\r\n@phouverneyuff you had some ideas on this as well, plz feel free to comment\r\n\r\n\r\nthis kind of implementation can co exit with current model if required or can completely replace the current one or could be a question during generation\r\n\r\nSample\r\n--------------\r\n```\r\n@RestController\r\n@RequestMapping(\"/api\")\r\npublic class BookResource {\r\n\r\n    private final static String RESOURCE = \"BOOK_RESOURCE\";\r\n\r\n    @RequestMapping(value = \"/books\",\r\n            method = RequestMethod.POST,\r\n            produces = MediaType.APPLICATION_JSON_VALUE)\r\n    @Timed\r\n    @Secured(RESOURCE)\r\n    public ResponseEntity<Void> create(@RequestBody Book book) throws URISyntaxException {\r\n        .....\r\n    }\r\n\r\n    @RequestMapping(value = \"/books\",\r\n            method = RequestMethod.GET,\r\n            produces = MediaType.APPLICATION_JSON_VALUE)\r\n    @Timed\r\n    @Secured(RESOURCE)\r\n    public List<Book> getAll() {\r\n        ...\r\n    }\r\n\r\n}\r\n```\r\n```\r\n/**\r\n * Custom method security configuration\r\n */\r\n@Configuration\r\n@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true, mode = AdviceMode.PROXY)\r\npublic class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {\r\n\r\n    public AccessDecisionManager accessDecisionManager() {\r\n        CustomAccessDecisionManager accessDecisionManager = new CustomAccessDecisionManager ();\r\n        return accessDecisionManager;\r\n    }\r\n}\r\n```\r\n```\r\n/**\r\n * Custom Access decision manager for method security\r\n */\r\npublic class CustomAccessDecisionManager implements AccessDecisionManager {\r\n\r\n    private final Logger log = LoggerFactory.getLogger(CustomAccessDecisionManager.class);\r\n\r\n    @Override\r\n    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {\r\n        ConfigAttribute configAttribute = SecurityUtils.getSingleConfigAttribute(configAttributes);\r\n\r\n        MethodInvocation methodInvocation = (MethodInvocation) object;\r\n\r\n        try {\r\n            if(authentication != null) {\r\n                User user = SecurityUtils.getCurrentUser(authentication);\r\n                RequestMapping requestMapping = null;\r\n                for (Annotation annotation : methodInvocation.getMethod().getDeclaredAnnotations()) {\r\n                    if(annotation instanceof RequestMapping){\r\n                        requestMapping = (RequestMapping) annotation;\r\n                    }\r\n                }\r\n\r\n                String method = SecurityUtils.getHttpMethod(requestMapping);\r\n                String resourceName = SecurityUtils.getResourceName(configAttribute);\r\n\r\n                if (user != null && ! hasAccessToResource(user, method, resourceName)) {\r\n                    throw new AccessDeniedException(\"User does not have enough privileges to access this method\");\r\n                }\r\n            }\r\n        } catch (ClassCastException e) {\r\n            throw new IllegalStateException();\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean supports(ConfigAttribute attribute) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public boolean supports(Class<?> clazz) {\r\n        return clazz.equals(MethodInvocation.class);\r\n    }\r\n    /**\r\n     * checks if the resource has grants in any roles for the user\r\n     * @param user\r\n     * @param method\r\n     * @param resourceName\r\n     * @return\r\n     */\r\n    private boolean hasAccessToResource(User user, String method, String resourceName) {\r\n/*\r\n*  The resources are converted into\r\n*  a map while retrieving user for first time and later use the map to fetch the section and check access.\r\n*/\r\n\r\n        Map<String, ResourceDTO> validResources = user.getValidResources();\r\n        if(validResources !=null && !validResources.isEmpty()) {\r\n            ResourceDTO validResource = validResources.get(resourceName);\r\n            if (validResource != null && hasGrants(validResource, method)) {\r\n                log.debug(\"Role has access to resource:{} \", validResource);\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n    /**\r\n     * checks for grant for HTTP methods\r\n     * GET = 1 = VIEW,\r\n     * POST = 2 = CREATE,\r\n     * PUT = 3 = EDIT,\r\n     * DELETE = 4 = DELETE\r\n     *\r\n     * @param resource\r\n     * @param method\r\n     * @return\r\n     */\r\n    private boolean hasGrants(ResourceDTO resource, String method) {\r\n        int grant = 0;\r\n        switch (method){\r\n            case \"GET\":\r\n                grant = 1; break;\r\n            case \"POST\":\r\n                grant = 2; break;\r\n            case \"PUT\":\r\n                grant = 3; break;\r\n            case \"DELETE\":\r\n                grant = 4; break;\r\n            default:\r\n                grant = 0;\r\n        }\r\n\r\n        return resource.getGrant() >= grant;\r\n    }\r\n\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/comments/216456690","html_url":"https://github.com/jhipster/generator-jhipster/issues/1995#issuecomment-216456690","issue_url":"https://api.github.com/repos/jhipster/generator-jhipster/issues/1995","id":216456690,"user":{"login":"jdubois","id":316835,"avatar_url":"https://avatars.githubusercontent.com/u/316835?v=3","gravatar_id":"","url":"https://api.github.com/users/jdubois","html_url":"https://github.com/jdubois","followers_url":"https://api.github.com/users/jdubois/followers","following_url":"https://api.github.com/users/jdubois/following{/other_user}","gists_url":"https://api.github.com/users/jdubois/gists{/gist_id}","starred_url":"https://api.github.com/users/jdubois/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdubois/subscriptions","organizations_url":"https://api.github.com/users/jdubois/orgs","repos_url":"https://api.github.com/users/jdubois/repos","events_url":"https://api.github.com/users/jdubois/events{/privacy}","received_events_url":"https://api.github.com/users/jdubois/received_events","type":"User","site_admin":false},"created_at":"2016-05-03T07:19:45Z","updated_at":"2016-05-03T07:19:45Z","body":"Oh yes, sorry. It's something I wanted to do but I forgot!"}}