📜 ⬆️ ⬇️

Security system Campus.ru

When developing almost any software product, sooner or later, developers face the problem of restricting access. For example, in a web application, some pages can be accessed only by administrators or only registered users. In the Campus.ru project, this access restriction is provided by the Spring Security library.

Spring Security works with static roles that are defined through a URI for each request from the HTTP client. But what about dynamic roles that change depending on the relationship between the current user and the requested object (for example, the role of the author in relation to the article)?


To solve the problem of access to individual entities, the Domain Object Security System from Spring can be used. However, this system is rigidly tied to the domain model, supports a limited number of functions on entities (up to 32) and is rather complex in implementation (7 key interfaces).
')
We decided to develop a new framework CRMedia.Security, which would have more flexibility, support dynamic access lists, and also be easier to use. The idea of ​​the framework proposed by us is that it allows you to protect certain user actions with a specific set of arguments, the meaning of which is known only to the web application, but not to the security system. Such a model of the system allows you to abstract away Campus.ru entities and use the framework in other projects.

CRMedia.Security gets the action, after which its arguments from the application look for restrictions on these actions through the PermissionProvider and calculate the intersection of these restrictions with the access list of the current user provided by the ACLProvider. If the intersection is an empty set, then a handler is called that corresponds to the access denied case. In the case of a non-empty intersection, the call to a specific Tapestry event continues.

Here is a specific scenario that requires verification of rights: for example, viewing the community with id = 10 articles under number 20. In this case, “viewing an article” is an action, and 10, 20 - its arguments. Suppose this article is available only to members of a community, and a non-community user is trying to view it. Thus, the PermissionProvider implemented in the application must return the “community member” constraint, and the ACLProvider is “not a member of the community”. The intersection of these sets is empty, and, therefore, access to the page of the post will be closed.

Consider a software implementation of a security system using the CRMedia.Security framework. First you need to connect the CRMedia.Security module to the application being developed. This module connects the annotation handler to the Tapestry component converter chain (ComponentClassTransformWorker).

The interface of the restriction provider (PermissionProvider) is as follows:

public interface PermissionProvider {

/**
*
* @param action
* @param permission ACL
*/
void restrict(Action action, List <PermissionEntry> permission);

/**
* ACL
* @param action
* @return ACL
*/
List <PermissionEntry> get (Action action);

/**
*
* @param action
*/
void revoke(Action action);

/**
*
* @param params
*/
void revokeReferenced(Map< String , Object> params );

}


* This source code was highlighted with Source Code Highlighter .


It can be implemented as a DAO for storing constraints in the database. Here Action is a collection of the name of the action and the list of named arguments ({view_article; community = 10; article = 20} for the example above), and PermissionEntry is one entry of the access list (for example, {status = member}).

ACLProvider contains the only method by which CRMedia.Security gets the current user's access list:

public interface ACLProvider {

/**
*
* @param component
* @param action
* @return ; null,
*/
List <PermissionEntry> getACL(Component component, Action action);

}


* This source code was highlighted with Source Code Highlighter .


For our example on action {view_article; community = 10; article = 20} the getACL method should return the set {status = nonmember} based on the relationship between the current user and the specified community.

Now you need to tell the security engine exactly which events should be protected. To do this, you must arrange annotations CRMedia.Security.

Let the post be viewed through the ViewArticle page. When accessing it using Tapestry rules, the onActivate method is called. To restrict access to it, we need to arrange annotations in the following way:

public class ViewArticle {

@Restricted(action = "view_article" )
Object onActivate(@SecuredParam( "community" ) Community community,
@SecuredParam( "article" ) Article article) {

...

}

}


* This source code was highlighted with Source Code Highlighter .


The Restricted annotation is placed before an event that has a restriction. Each event corresponds to one action (in this case, “view_article”) with a specific set of arguments (“community” and “article”). Arguments from this are accompanied by SecuredParam annotations.

By default, when access is denied, the client receives a response from the server with the code 403 (Forbidden). If necessary, you can use your handler:

public class ViewArticle {

@Restricted(action = "view_article" )
void onActivate(@SecuredParam( "community" ) Community community,
@SecuredParam( "article" ) Article article) {

...

}

Object onForbidForListArticles() {
return new TextStreamResponse( "text/plain" , "access denied" );
}

}


* This source code was highlighted with Source Code Highlighter .


CRMedia.Security supports nested properties in the arguments being checked. For example, in the following example, the “community” parameter is taken from the community property of the Article entity:

public class Article {

public Community getCommunity() {

}

}

public class ViewArticle {

@Restricted(
action = "view_article" ,
params = {
@SecuredProp(name = "community" , paramProp = "article.community" )
}
)
Object onActivate(@SecuredParam( "article" ) Article article) {

...

}

}


* This source code was highlighted with Source Code Highlighter .


If the page retains its state between requests, the values ​​of the action arguments for the protected event (“view” in the example below) can be taken from the page itself:
public class ViewArticle {

@Property
@Persist
private Article article;

@Restricted(
action = "view_article" ,
params = {
@SecuredProp(name = "article" , pageProp = "article" ),
@SecuredProp(name = "community" , paramProp = "article.community" )
}
)
Object onView() {

...

}

}


* This source code was highlighted with Source Code Highlighter .


In addition to restrictions on events, you can set a limit on viewing parts of the page. To do this, there is an IfCan component in the framework, which is completely similar to using the If component from Tapestry Core in the template:

< html xmlns ="http://www.w3.org/1999/xhtml" xmlns:t ="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" >
< body >
< t:ifCan actionName ="view_article" context ="viewArticleContext" >
< t:eventlink event ="view" > </ t:eventlink >
< t:parameter name ="else" >

</ t:parameter >
</ t:ifCan >
</ body >
</ html >


* This source code was highlighted with Source Code Highlighter .


The viewArticleContext property, similar to SecuredProp annotations, defines a list of protected parameters:

public class ViewArticle {

@Property
@Persist
private Article article;

public Object[] getViewArticleContext() {
return new Object[]{ "article" , article, "community" , article.getCommunity()};
}

@Restricted(
action = "view_article" ,
params = {
@SecuredProp(name = "article" , pageProp = "article" ),
@SecuredProp(name = "community" , paramProp = "article.community" )
}
)
Object onView() {

...

}

}


* This source code was highlighted with Source Code Highlighter .


Thus, the developed framework has a sufficient degree of abstraction, allows you to protect various events on the Tapestry pages from unauthorized execution and to customize the display of the page depending on the rights that the user has.

Source: https://habr.com/ru/post/57525/


All Articles