A rails application with a complete RBAC authorization system (part 1)

Source: Internet
Author: User

This is a simple RBAC authorization system. In fact, it is not simple, but everything becomes very easy under the world-famous rails framework. Of course, we need to trust the two plug-ins we will use later. Let's talk about our project. This is a wiki-like website. You can say that it is a simple version. Its development code has been set as wiki! The project requirement is also very simple. Users can publish things after registration. However, this does not seem to require any authorization system, so we have to make it a little more complicated. By the way, our authorization system is the famous Role Based Access Control (RBAC, role-Based Access Control ). In the ever-changing authorization requirements, we only need to introduce a new role to flatten them. This is because roles exist in RBAC as a set of a certain number of permissions. In addition, rails has already provided a large number of such plug-ins, which is synonymous with xunmin development. Therefore, it is not easy to say.

Now let's review our project and ask the user to post it only after registering and logging in.ArticleHere, let's talk about the regular expression. You can post the entry (Lemma ). Well, here we have the first resource lemma. Of course, if there are more than one entry, it will be difficult to do, so we need to introduce the trendy tag cloud. To encourage users to create entries or write high-quality entries, each entry has certain credits by default. This credit is obtained after the user completes the entry. Some entries may not be easy to write, so we need to increase the gold content of these entries. Although this is an attribute of the entry, we cannot allow users to cheat by editing this attribute. In order to prevent something that is out of stock or other reactionary and illegal content from going out on our website, we must establish a moderator system similar to BBS, which I call peace maker ), of course, we also need to ignore the existence of all rules and remove incompetent peace makers from the management layer. I call it provisionbreake (a person who violates God's mind ). Peace maker can change the points of a certain entry, but cannot add points to itself or others. The bonus points are automatically executed by the system. As a wiki, there is certainly a co-written system. We can let users go through a certain step to become co-creators of the entries created by others, but it is impossible to earn points without making contributions to attendance, add points only after modification, and do not add points again. Therefore, we must set a switch here. Undoubtedly, there is a many-to-many relationship between the entry and the user. An intermediate table is required between them! Well, this is basically the case. When thinking about other aspects, rails does not need to be written in a great way like Java at the very beginning, so it is not the development of xunmin!

Our development tool is netbeans, and the Environment is Window XP, Ruby 1.8.6, and rails 2.3.2. Because we will use the new features of 2.3 in the future, please upgrade to rails2.3. Let's get started.

Create a new project.

Specify the database.

Create a database.

Next, install the plug-in. The first is the famous restful-authentication. We need to use its login verification as RubyCommunityThe result of the longest discussion string in railsforum.com is definitely trustworthy. The second is declarative authorization. Our authorization system depends on it. What are its advantages? Provides authorization control from the view to the Controller to the model, and CALLS authorization rules in a declarative manner to separate the authorization logic from the business logic, so that authorization rules can be compiled in a configuration file using a user-friendly DSL, provides a graphical interface for you to straighten out complex authorization systems. But now let's open the console and locate the project root directory:

 
Ruby script/plugin install git: // github.com/javasweenie/restful-authentication.gitgit clone git: // github.com/stffn/declarative_authorization.git vendor/plugins/declarative_authorization

Note: Install git here. Google yourself!

Create the user module and session module. In order to go straight to the topic, we do not need to activate registration, automatic login, and other fashionable features. Therefore, as prompted when installing the plug-in, just enter:

 
Ruby script/generate authenticated user sessionsrake DB: migrate

In the RBAC Authorization Model, user and session represent both two resources and two important elements. In a general authorization system, a user exists as the owner or entity of the permission. In the RBAC authorization model, the situation is slightly changed. Specifically, the user is the additional entity or host of the permission. The permission is moved to the role we will mention later. Session is an obscure element in RBAC. Needless to say, the session first solves the Web stateless problem for us. This work is mainly done by restful-authentication. Second, we can use session to host all authorized users. This is very important. As mentioned above, in the RBAC authorization model, the user is only a pure user and the permissions have been stripped away. A user cannot be directly associated with privilege. To have a user to operate on a certain resource, it must be associated through role. If the user is registered, we can assign a role such as user (registered user) or wikier (Weike). However, if the user is not registered or has not logged in, isn't it difficult for a website with many levels to implement? A website always has some public resources, which is not contrary to our original intention. Therefore, we require that a user be assigned a role even if the user has not logged on. This is the responsibility of declarative_authorization.

Open users_controller and sessions_controller and comment out include authenticatedsystem as required.

Modify application_controller, where, CURRENT_USER, logged_in? All are from restful-Authentication plug-ins. For security purposes, we also use the filter_parameter_logging method to filter sensitive fields such as password out of the log to prevent hackers from spying!

Next, this step is very important. We need to add the roles field to the user table, that is, the role element of RBAC. It is the unit and carrier of permission allocation, and is the user (users) and permission (permissions) is used to decouple the relationship between permissions and users. We can also separate the role into a module so that the user can have multiple roles at the same time, but now we can simplify everything to reduce the join operation of the database.

 
Ruby script/generate migration addrolestousers roles: Text

 
Class addrolestousers "wikier" End def self. Down remove_column: users,: Roles endend

Then:

 
Rake DB: migrate

Before starting the user module, we still do some locking work, which will make our future work look pleasing to the eye. There is no error, that is, the global template of the planning project. Under the APP/views/layouts/directory, define application.html. ERB.CodeIs:

It uses many custom methods. We create layout_help.rb in the app/helper directory and put them in it:

Module layouthelper def title (page_title, show_title = true) @ content_for_title = page_title.to_s @ show_title = show_title end def show_title? @ Show_title end def stylesheet (* ARGs) content_for (: Head) {stylesheet_link_tag (* args. map (&: to_s)} end def JavaScript (* ARGs) ARGs = args. map {| arg =: defaults? Arg: Arg. to_s} content_for (: Head) {javascript_include_tag (* ARGs)} endend

Modify users_controller to improve its restful function. Before_filter is used, not only for dry, but also for later access control. The modified code is as follows:

Class userscontroller [: Show,: Edit,: Update,: Destroy] before_filter: new_user,: Only =>: New def index @ users = user. all end def new; end def create logout_keeping_session! @ User = user. New (Params [: User]) Success = @ user & @ user. save if success & @ user. errors. Empty? Self. CURRENT_USER = @ user #!! Now logged in redirect_to users_url FLASH [: Notice] = "registration successful. "Else FLASH [: Error] =" registration failed. "Render: Action => 'new' end def show; end def edit; end def update if @ user. update_attributes (Params [: User]) Flash [: Notice] = "User updated successfully. "Redirect_to @ user else render: Action => 'edit' end def destroy @ user. Destroy FLASH [: Notice] =" the user is deleted successfully. "Redirect_to users_url end protected def load_user @ user = user. Find Params [: Id] End def new_user @ user = user. New endend

Modify _user_bar.html. ERB

<Div id = "user_bar"> <% if logged_in? %> <% = Link_to "logout", logout_path,: Title => "log in" %> <% = link_to "Welcome," + current_user.login + "! ", CURRENT_USER %> <% = link_to" user center ", CURRENT_USER %> <% = link_to" user list ", users_path if controller_name! = "Users" %> <% else %> <% = link_to "login", login_path,: Title => "log in" %> <% = link_to "register ", signup_path,: Title => "create an account" %> <% end %> </div>

Add users # index View

<%-Title "User List"-%> <Table> <tbody> <tr> <TH> account: </Th> <TH> role: </Th> <TH> operation: </Th> </tr> <%-@ users. each do | user |-%> <tr> <TD> <B> <% = link_to user. login, USER %> </B> </TD> <% = H user. roles. map (&: to_s) * ', 'If user. roles %> </TD> <% = link_to 'edit', [: Edit, user] %> <% = link_to 'delete', user ,: confirm => 'Are you sure? ',: Method =>: Delete %> </TD> </tr> <%-end-%> </tbody> </table>

Add users # Show View

<% Title "user center" %> <Table> <tbody> <tr> <TH> account: </Th> <TD> <% = link_to @ user. login, [: Edit, @ user] %> </TD> </tr> <TH> Email: </Th> <TD> <% = @ user. email %> </TD> </tr> <TH> role </Th> <TD> <% = H @ user. roles. map (&: to_s) * ', 'If @ user. roles %> </TD> </tr> </tbody> </table> <% = link_to "return", users_url %>

Delete publicindex.html and add new routing rules in routes. RB.

Actioncontroller: routing: routes. draw do | map. logout'/logout',: controller => 'session',: Action => 'deststroy' map. login '/login',: controller => 'session',: Action => 'new' map. register '/register',: controller => 'users',: Action => 'create' map. signup '/signup',: controller => 'users',: Action => 'new' map. resources: Users map. resource: Session map. root: Users map. connect ': Controller/: Action/: id' map. connect ': Controller/: Action/: ID.: Format 'end

Start rails:

 
Ruby script/Server

Modify users # New View

<% Title "new user" %> <% @ user. password = @ user. password_confirmation = nil %> <fieldset> <% = error_messages_for: USER %> <% form_for @ user do | f |-%> <p> <% = f. label: Login, 'account: '%> <br/> <% = f. text_field: Login %> </P> <p> <% = f. label: email, 'email: '%> <br/> <% = f. text_field: email %> </P> <p> <% = f. label: Password, 'password: '%> <br/> <% = f. password_field: Password %> </P> <p> <% = f. label: password_confirmation, 'Confirm password: '%> <br/> <% = f. password_field: password_confirmation %> </P> <p> <% = f. submit 'registration' %> </P> <% end %> </fieldset>

Now, let's create edit.html. ERB and process this important field roles. The user module is usually an application.ProgramMost importantly, operations such as editing and deletion can only be performed by the Administrator. Even if the user is able to cope with such sensitive areas, it is generally a castrated editing interface. For example, the login and password in the user model are used to allow the user to log on to the program, which is generally not modified. Therefore, we disabled this field in the form. The password is encrypted with sha1 and cannot be decrypted in reverse mode, no modification is provided. To protect the security of most fields in the user model, we also use the "whitelist". This restful_authentication plug-in has already been used for us, but the roles field was added later, we need to modify it. Add roles at the end of the attr_accessible method of the user model, that is:

 
Attr_accessible: Login,: email,: name,: Password,: password_confirmation,: Roles

Then add the following code to the model (super important)

 
Def role_symbols @ role_symbols | = (roles | []). Map {| r. to_sym} End

Create users # edit View

<% Title "edit user" %> <fieldset> <% form_for (@ user) Do | f |%> <% = f. error_messages %> <p> <% = f. label: Login, "Account" %> <br/> <% = f. text_field: Login,: Disabled => true %> </P> <p> <% = f. label: email, "email" %> <br/> <% = f. text_field: email %> </P> <p> <% = f. label: Roles, "role" %> <br/> <% = f. select: Roles, [['vice', 'wikier '], ['guardian of Order', 'Peace _ Manual'], ['person against God ', 'provision_breaker '], {: include_blank => "select",: Selected => 0 }%> </P> <p> <% = f. submit "Update" %> </P> <% end %> </fieldset> <% = link_to "return", url_for (: Back) %>

At this point, there is no restful authentication, so we can focus on the development of the authorization system.

Open Environment. Rb and add:

Config. load_once_paths + = % W (# {rails_root}/LIB)

This is used to ensure that the declarative_authorization plug-in will not load errors.

Open application_controller and add

 
Before_filter: set_current_user protected def set_current_user authorization. CURRENT_USER = CURRENT_USER end

This global pre-filter will place the CURRENT_USER of restful authentication into the same name method CURRENT_USER of declarative authorization. Note that both CURRENT_USER are session objects, which are loaded with our user objects. This ensures that user information can be saved on multiple pages. If the CURRENT_USER of restful authentication is empty, declarative authorization creates an anonymous user object and puts it into the CURRENT_USER of declarative authorization. It has a role_symbols attribute (that is why we need to add a role_symbols Method to the Custom User Model) and the value is guest. In essence, guest is the default role preset by declarative authorization. It is called when a request is not associated with any user or when a user does not have any role. In this way, the association between user and role is realized.

Source code of anonymous user, which is located in {rails_root}/vendor \ plugins \ declarative_authorization \ Lib \ declarative_authorization \ authorization. Rb

 
# Represents a pseudo-user to facilitate Guest users in applications class guestuser attr_reader: role_symbols def initialize (roles = [: Guest]) @ role_symbols = roles end

Open users_controller and enableController-level access control.

 
Class userscontroller [: Show,: Edit,: Update,: Destroy] before_filter: new_user,: Only =>: New filter_access_to: All filter_access_to [: Show,: Edit,: update],: attribute_check => true #.................. End

Two terms are involved:Coarse-grained and fine-grained.

Coarse granularity: Indicates the category level. Only the class of the object is checked, and a specific instance is not pursued.

Fine Granularity: Indicates the instance level, that is, the instance of the specific object needs to be considered ). For example, you can only view and edit your user information. This can be achieved through comparison ID or a specific attribute. Therefore, arrtibute_check => true (enable attribute check) is prepared for this purpose.

What's more, we can also enableModel-level access controlThe system will rewrite the query command based on your permissions to prevent data from being manipulated illegally.

Require 'digest/sha1' class user

However, due to the restful authentication plug-in, when we log on to sessions # create action, restful authentication not only changes the status of the session object, it also tries to change the state of the user object in the session object, and we generally do not allow the user to change the state of the user object without Logon (this is equivalent to update !), An error is reported.

 
Authorization: notauthorized (no matching rules found for update for # <authorization: guestuser: 0x4c1b7c8 @ role_symbols = [: Guest]> (roles [: Guest], privileges [: update,: Manage], context: Users ).): ## it requires our guest role to have the update and mange privileges. Generally, we only allow the guest to have the read_index and create privileges on users resources. # This attribute defines the content of the authorization rule later. Let's look at a few more sections to understand it!

Therefore, we stillDiscard model-level authorization control for the user modelRight.

In additionView-level access controlDirectly block links on the page. Correspondingly, controller-level access control blocks URLs in the address bar and model-level access control blocks hackers. The first two may be caused by misoperations, and the last one is definitely not good. View-level access control. Because authorization rules are not customized, it is difficult to give you an intuitive effect.

To separate authorization rules from business logic, declarative authorization specifies to define all authorization rules in a configuration file, and provides a special DSL for us to compile authorization rules. First, copy the sample authorization_rules.dist.rb under the \ vendor \ plugins \ declarative_authorization directory to the config directory and rename it authorization_rules.rb. Open authorization_rules.rb and remove the comment. It should look like this:

 
Authorization do role: Guest do endendprivileges do privilege: Manage,: Primary des => [: Create,: read,: Update,: delete] privilege: read,: Primary des => [: index,: Show] privilege: Create,: Includes =>: New privilege: Update,: Includes =>: Edit privilege: delete,: Includes =>: destroyend

The entire code is divided into two major blocks: Authorization block and privileged block.

first look at the privileged block. Five privileges, four simple privileges (: read,: Update,: Create,: delete), and one composite privilege (: Manage) have been given ). Include is followed by the Controller action name. In Rails Applications, actions are required to operate a resource. For some actions, it is based on another action. For example, the update action must first pass through the edit action, and then the form on edit.html is submitted to the update action through the edit action. Therefore, the four simple privileges can be used to construct access control for the five restful actions, index, show, edit, update, and destroy. If you are not satisfied, you can also customize the action and then perform access control on it.

let's look at the authorization block, which is used to assign permissions. Note that the permission is not a privilege ). In the new privileged block, all the privileges are access control for the action of the controller of some resources, but no specific resource category is specified, here we need to convert these privileges into permissions. Since permission is used, who is "restricted? Well, it is not hard to see that it is restricted to a role or a certain resource. Only a specific role can perform specific operations on a specific resource, in addition, it must meet certain requirements (for example, to enable attribute check in the controller, the specific check method is defined here ). A popular formula is as follows: permission = Resource + operation . Here, we have to work around it. All operations are encapsulated in the privilege. A privilege may have n operations, and there are also conditions for property reporting and querying. Therefore, permission = Resource * privileged if condition . A role may be able to operate on multiple resources. Therefore, in a sense, A role is a set of a certain number of permissions . After learning about this, let's write some authorization rules. By default, a guest role has been assigned to us. It will be called when the request is not associated with any user or when a user has no role. Therefore, if our application has some public pages, you can use guest to access those users who are not logged on. At present, our application is very small, with only two resources (user and session ). In order to allow users to log on freely, we should not set authorization control in the session. User resources. We plan to allow access by the index to anyone, and show and edit can only be accessed by this user. This requires logon to become wikier, attribute check is basically not required to determine whether it is the same person!

Authorization do role: Guest do has_permission_on: users,: To => [: read_index,: Create] End role: wikier do includes: Guest has_permission_on: users,: To => [: read_show,: update] Do if_attribute: Id => is {user. id} end role: providence_breaker do has_permission_on: users,: To =>: Manage endprivileges do privilege: Manage,: Primary des => [: Create,: read,: Update ,: delete] privilege: read,: Primary des => [: Index,: Show] privilege: read_index,: Primary des =>: Index privilege: read_show,: Primary des =>: Show privilege: create,: Includes =>: New privilege: Update,: Includes =>: Edit privilege: delete,: Includes =>: destroyend

Restart our application Wiki and modify environment. RB. Test its authorization control when we do not log on.

According to the authorization rules, when we do not log on, we are assigned the guest role, which can access users # index and click the above five links (circled in red ). The logon and registration capabilities are found. The following three pages are displayed on a white page: "You are not allowed to access this action ."

Access fails if you do not have sufficient permissions.

If you are not satisfied with this English, you can add a protected action to the current controller.

Class userscontroller

When we register, we will immediately become a wiki ). VIKE has all the permissions for tourists (also des: Guest ). Well, I think it's time to explain the meaning of the authorization rules. The declarative authorization plug-in provides powerful DSL commands, making it easy to interpret the code. For example:

 
Has_permission_on: users,: To => [: read_index,: Create]

Combined with the abovePermission = Resource * privileged if conditionFormula: this permission allows the role to use the read_index and: create privileges in the resource users without restriction, and then the user-friendly point, so that the role can freely access users # index (view and corresponding action) and users # New (view and corresponding action) and users # create (only action ). Note: In order to segment the coarse granularity of permissions, we have added two new privileges: read_index and: read_show.

 
Includes: Guest

As mentioned earlier, all permissions of the inherited role: Guest.

Has_permission_on: users,: To => [: read_show,: update] Do if_attribute: Id =>is {user. ID} End

To access users # Show and users # edit and users # update, you must meet the conditions with the same IDs. If_attribute: ID in ID, which is the ID of the resource you want to access. In this example, the @ user pre-retrieved by users_controller using before_filter. The user in {user. ID} always refers to CURRENT_USER, that is, the anonymous user object allocated to us by the plug-in before login, or our own user object after login.

When our role is a VIP, we can also access two areas that we could not access (users # Show and users # index! But for tourists, this is a very depressing thing-"I should not be able to see the areas I cannot access. Let me click to see such a disappointing thing !" Therefore, we need to use access control at the view level, so that those who do not have the corresponding permissions can not see them, even if they view the source code.

This is a page for visitors.

This is the page seen by VIKE (a common registered user.

This is the page that the Administrator sees. So far, a complete authorization system has been completed, and more advanced applications of this plug-in will be introduced in the next section. CSS attachment: Click here to download!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.