Commit 8373e2f6227bd0a3d9d7a53f0ccf2569565bb65f
Committed by
Rodrigo Souto
1 parent
c4728894
Exists in
master
and in
29 other branches
adding authentication and entity mananegement
Showing
10 changed files
with
302 additions
and
37 deletions
Show diff stats
app/models/article.rb
@@ -790,12 +790,6 @@ class Article < ActiveRecord::Base | @@ -790,12 +790,6 @@ class Article < ActiveRecord::Base | ||
790 | true | 790 | true |
791 | end | 791 | end |
792 | 792 | ||
793 | - #FIXME make this test | ||
794 | - #Define which parameters will be returned in json object | ||
795 | - def as_json(options = {}) | ||
796 | - super(:only => [:id, :name, :body, :created_at], :methods => [:title]) | ||
797 | - end | ||
798 | - | ||
799 | private | 793 | private |
800 | 794 | ||
801 | def sanitize_tag_list | 795 | def sanitize_tag_list |
app/models/user.rb
1 | require 'digest/sha1' | 1 | require 'digest/sha1' |
2 | require 'user_activation_job' | 2 | require 'user_activation_job' |
3 | +require 'securerandom' | ||
3 | 4 | ||
4 | # User models the system users, and is generated by the acts_as_authenticated | 5 | # User models the system users, and is generated by the acts_as_authenticated |
5 | # Rails generator. | 6 | # Rails generator. |
@@ -119,6 +120,18 @@ class User < ActiveRecord::Base | @@ -119,6 +120,18 @@ class User < ActiveRecord::Base | ||
119 | self.update_attribute :last_login_at, Time.now | 120 | self.update_attribute :last_login_at, Time.now |
120 | end | 121 | end |
121 | 122 | ||
123 | + #FIXME make this test | ||
124 | + def generate_private_token! | ||
125 | + self.private_token = SecureRandom.hex | ||
126 | + self.private_token_generated_at = DateTime.now | ||
127 | + save(false) | ||
128 | + end | ||
129 | + | ||
130 | + #FIXME make this test | ||
131 | + def private_token_expired? | ||
132 | + self.generate_private_token! if self.private_token.nil? || (self.private_token_generated_at + 2.weeks < DateTime.now) | ||
133 | + end | ||
134 | + | ||
122 | # Activates the user in the database. | 135 | # Activates the user in the database. |
123 | def activate | 136 | def activate |
124 | return false unless self.person | 137 | return false unless self.person |
db/migrate/20140407013817_add_private_token_info_to_users.rb
1 | class AddPrivateTokenInfoToUsers < ActiveRecord::Migration | 1 | class AddPrivateTokenInfoToUsers < ActiveRecord::Migration |
2 | def self.up | 2 | def self.up |
3 | - add_column :users, :private_token | 3 | + add_column :users, :private_token, :string |
4 | add_column :users, :private_token_generated_at, :datetime | 4 | add_column :users, :private_token_generated_at, :datetime |
5 | end | 5 | end |
6 | 6 |
lib/api/api.rb
1 | require 'grape' | 1 | require 'grape' |
2 | +Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} | ||
2 | 3 | ||
3 | module API | 4 | module API |
4 | class API < Grape::API | 5 | class API < Grape::API |
@@ -9,10 +10,10 @@ module API | @@ -9,10 +10,10 @@ module API | ||
9 | 10 | ||
10 | mount V1::Articles | 11 | mount V1::Articles |
11 | mount V1::Comments | 12 | mount V1::Comments |
13 | + mount V1::Users | ||
14 | + mount Session | ||
12 | 15 | ||
13 | -# helpers APIHelpers | ||
14 | - | ||
15 | -# require Articles | 16 | + helpers APIHelpers |
16 | 17 | ||
17 | end | 18 | end |
18 | end | 19 | end |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +module API | ||
2 | + module Entities | ||
3 | + class Article < Grape::Entity | ||
4 | + expose :id, :name, :body, :created_at | ||
5 | +# expose :is_admin?, as: :is_admin | ||
6 | + | ||
7 | +# expose :avatar_url do |user, options| | ||
8 | +# if user.avatar.present? | ||
9 | +# user.avatar.url | ||
10 | +# end | ||
11 | +# end | ||
12 | + end | ||
13 | + | ||
14 | + class Comment < Grape::Entity | ||
15 | + expose :author_id, :body, :title, :created_at | ||
16 | + end | ||
17 | + | ||
18 | + class User < Grape::Entity | ||
19 | + expose :login | ||
20 | + end | ||
21 | + | ||
22 | + class UserLogin < User | ||
23 | + expose :private_token | ||
24 | + end | ||
25 | + | ||
26 | + end | ||
27 | +end |
@@ -0,0 +1,108 @@ | @@ -0,0 +1,108 @@ | ||
1 | +module API | ||
2 | + module APIHelpers | ||
3 | + PRIVATE_TOKEN_PARAM = :private_token | ||
4 | + | ||
5 | + def current_user | ||
6 | + private_token = params[PRIVATE_TOKEN_PARAM].to_s | ||
7 | + @current_user ||= User.find_by_private_token(private_token) | ||
8 | + @current_user = nil if !@current_user.nil? && @current_user.private_token_expired? | ||
9 | + @current_user | ||
10 | + end | ||
11 | + | ||
12 | + def logout | ||
13 | + @current_user = nil | ||
14 | + end | ||
15 | + | ||
16 | + | ||
17 | +# def paginate(relation) | ||
18 | +# per_page = params[:per_page].to_i | ||
19 | +# paginated = relation.page(params[:page]).per(per_page) | ||
20 | +# add_pagination_headers(paginated, per_page) | ||
21 | +# | ||
22 | +# paginated | ||
23 | +# end | ||
24 | + | ||
25 | + def authenticate! | ||
26 | + unauthorized! unless current_user | ||
27 | + end | ||
28 | + | ||
29 | +# def authenticated_as_admin! | ||
30 | +# forbidden! unless current_user.is_admin? | ||
31 | +# end | ||
32 | +# | ||
33 | +# def authorize! action, subject | ||
34 | +# unless abilities.allowed?(current_user, action, subject) | ||
35 | +# forbidden! | ||
36 | +# end | ||
37 | +# end | ||
38 | +# | ||
39 | +# def can?(object, action, subject) | ||
40 | +# abilities.allowed?(object, action, subject) | ||
41 | +# end | ||
42 | + | ||
43 | + # Checks the occurrences of required attributes, each attribute must be present in the params hash | ||
44 | + # or a Bad Request error is invoked. | ||
45 | + # | ||
46 | + # Parameters: | ||
47 | + # keys (required) - A hash consisting of keys that must be present | ||
48 | + def required_attributes!(keys) | ||
49 | + keys.each do |key| | ||
50 | + bad_request!(key) unless params[key].present? | ||
51 | + end | ||
52 | + end | ||
53 | + | ||
54 | + def attributes_for_keys(keys) | ||
55 | + attrs = {} | ||
56 | + keys.each do |key| | ||
57 | + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) | ||
58 | + end | ||
59 | + attrs | ||
60 | + end | ||
61 | + | ||
62 | + # error helpers | ||
63 | + | ||
64 | + def forbidden! | ||
65 | + render_api_error!('403 Forbidden', 403) | ||
66 | + end | ||
67 | + | ||
68 | + def bad_request!(attribute) | ||
69 | + message = ["400 (Bad request)"] | ||
70 | + message << "\"" + attribute.to_s + "\" not given" | ||
71 | + render_api_error!(message.join(' '), 400) | ||
72 | + end | ||
73 | + | ||
74 | + def not_found!(resource = nil) | ||
75 | + message = ["404"] | ||
76 | + message << resource if resource | ||
77 | + message << "Not Found" | ||
78 | + render_api_error!(message.join(' '), 404) | ||
79 | + end | ||
80 | + | ||
81 | + def unauthorized! | ||
82 | + render_api_error!('401 Unauthorized', 401) | ||
83 | + end | ||
84 | + | ||
85 | + def not_allowed! | ||
86 | + render_api_error!('Method Not Allowed', 405) | ||
87 | + end | ||
88 | + | ||
89 | + def render_api_error!(message, status) | ||
90 | + error!({'message' => message}, status) | ||
91 | + end | ||
92 | + | ||
93 | +# private | ||
94 | +# | ||
95 | +# def add_pagination_headers(paginated, per_page) | ||
96 | +# request_url = request.url.split('?').first | ||
97 | +# | ||
98 | +# links = [] | ||
99 | +# links << %(<#{request_url}?page=#{paginated.current_page - 1}&per_page=#{per_page}>; rel="prev") unless paginated.first_page? | ||
100 | +# links << %(<#{request_url}?page=#{paginated.current_page + 1}&per_page=#{per_page}>; rel="next") unless paginated.last_page? | ||
101 | +# links << %(<#{request_url}?page=1&per_page=#{per_page}>; rel="first") | ||
102 | +# links << %(<#{request_url}?page=#{paginated.total_pages}&per_page=#{per_page}>; rel="last") | ||
103 | +# | ||
104 | +# header 'Link', links.join(', ') | ||
105 | +# end | ||
106 | + | ||
107 | + end | ||
108 | +end |
@@ -0,0 +1,60 @@ | @@ -0,0 +1,60 @@ | ||
1 | +module API | ||
2 | + | ||
3 | +# require 'api/validations/uniqueness' | ||
4 | + | ||
5 | + # Users API | ||
6 | + class Session < Grape::API | ||
7 | +#params do | ||
8 | +# requires :login, :uniqueness => true | ||
9 | +#end | ||
10 | + | ||
11 | + # Login to get token | ||
12 | + # | ||
13 | + # Parameters: | ||
14 | + # login (*required) - user login or email | ||
15 | + # password (required) - user password | ||
16 | + # | ||
17 | + # Example Request: | ||
18 | + # POST /session | ||
19 | + get "/login" do | ||
20 | +# post "/session" do | ||
21 | +environment = nil #FIXME load the correct environment create a method in helper | ||
22 | + user ||= User.authenticate(params[:login], params[:password], environment) | ||
23 | + | ||
24 | + return unauthorized! unless user | ||
25 | + user.generate_private_token! | ||
26 | + present user, :with => Entities::UserLogin | ||
27 | + end | ||
28 | + | ||
29 | + # Create user. | ||
30 | + # | ||
31 | + # Parameters: | ||
32 | + # email (required) - Email | ||
33 | + # password (required) - Password | ||
34 | + # name - Name | ||
35 | + # Example Request: | ||
36 | + # POST /users | ||
37 | +# post do | ||
38 | + get "register" do | ||
39 | + required_attributes! [:email, :login, :password] | ||
40 | + attrs = attributes_for_keys [:email, :login, :password] | ||
41 | + attrs[:password_confirmation] = attrs[:password] | ||
42 | + user = User.new(attrs) | ||
43 | +begin | ||
44 | + if user.save | ||
45 | + present user, :with => Entities::User | ||
46 | + else | ||
47 | + not_found! | ||
48 | + end | ||
49 | +rescue | ||
50 | +# not_found! | ||
51 | +#FIXME See why notfound is not working | ||
52 | +{} | ||
53 | +end | ||
54 | +# user | ||
55 | + end | ||
56 | + | ||
57 | + | ||
58 | + | ||
59 | + end | ||
60 | +end |
lib/api/v1/articles.rb
1 | module API | 1 | module API |
2 | module V1 | 2 | module V1 |
3 | - class Articles < Grape::API | ||
4 | - | ||
5 | - resource :articles do | 3 | + class Articles < Grape::API |
6 | 4 | ||
7 | - get do | ||
8 | - first_update = DateTime.parse(params[:first_update]) if params[:first_update] | ||
9 | - last_update = DateTime.parse(params[:last_update]) if params[:last_update] | 5 | + before { authenticate! } |
6 | + | ||
7 | + resource :articles do | ||
10 | 8 | ||
11 | - if first_update.nil? | ||
12 | - begin_date = Article.first.created_at | ||
13 | - end_date = last_update.nil? ? DateTime.now : last_update | ||
14 | - else | ||
15 | - begin_date = first_update | ||
16 | - end_date = DateTime.now | 9 | + #FIXME See if it's possible to use pagination instead of DateTime control. see a way to use this pagination logic genericaly |
10 | + get do | ||
11 | + first_update = DateTime.parse(params[:first_update]) if params[:first_update] | ||
12 | + last_update = DateTime.parse(params[:last_update]) if params[:last_update] | ||
13 | + | ||
14 | + if first_update.nil? | ||
15 | + begin_date = Article.first.created_at | ||
16 | + end_date = last_update.nil? ? DateTime.now : last_update | ||
17 | + else | ||
18 | + begin_date = first_update | ||
19 | + end_date = DateTime.now | ||
20 | + end | ||
21 | + | ||
22 | + limit = params[:limit].to_i | ||
23 | + limit = 20 if limit == 0 | ||
24 | + conditions = {} | ||
25 | + conditions[:type] = params[:content_type] if params[:content_type] #FIXME validate type | ||
26 | + conditions[:created_at] = begin_date...end_date | ||
27 | + present Article.find(:all, :conditions => conditions, :offset => (first_update.nil? ? 0 : 1), :limit => limit, :order => "created_at DESC"), :with => Entities::Article | ||
28 | + end | ||
29 | + | ||
30 | + #FIXME load article with environment context | ||
31 | + get ':id' do | ||
32 | + present Article.find(params[:id]), :with => Entities::Article | ||
17 | end | 33 | end |
18 | 34 | ||
19 | - limit = params[:limit].to_i | ||
20 | - limit = 20 if limit == 0 | ||
21 | - conditions = {} | ||
22 | - conditions[:type] = params[:content_type] if params[:content_type] #FIXME validate type | ||
23 | - conditions[:created_at] = begin_date...end_date | ||
24 | - Article.find(:all, :conditions => conditions, :offset => (first_update.nil? ? 0 : 1), :limit => limit, :order => "created_at DESC") | ||
25 | - end | ||
26 | - | ||
27 | - get ':id' do | ||
28 | - Article.find(params[:id]) | 35 | + #FIXME load article with environment context |
36 | + get ':id/children' do | ||
37 | + present Article.find(params[:id]).children, :with => Entities::Article | ||
38 | + end | ||
39 | + | ||
40 | + #FIXME load article with environment context | ||
41 | + get ':id/children/:child_id' do | ||
42 | + present Article.find(params[:id]).children.find(params[:child_id]), :with => Entities::Article | ||
43 | + end | ||
44 | + | ||
45 | + | ||
29 | end | 46 | end |
47 | + | ||
30 | end | 48 | end |
31 | - | ||
32 | - end | ||
33 | end | 49 | end |
34 | end | 50 | end |
lib/api/v1/comments.rb
1 | module API | 1 | module API |
2 | module V1 | 2 | module V1 |
3 | class Comments < Grape::API | 3 | class Comments < Grape::API |
4 | + | ||
5 | + before { authenticate! } | ||
4 | 6 | ||
5 | resource :articles do | 7 | resource :articles do |
6 | - | 8 | + #FIXME make the pagination |
9 | + #FIXME put it on environment context | ||
7 | get ":id/comments" do | 10 | get ":id/comments" do |
8 | - Article.find(params[:id]).comments | 11 | + present Article.find(params[:id]).comments, :with => Entities::Comment |
9 | end | 12 | end |
10 | 13 | ||
11 | get ":id/comments/:comment_id" do | 14 | get ":id/comments/:comment_id" do |
12 | - Article.find(params[:id]).comments.find(params[:comment_id]) | 15 | + present Article.find(params[:id]).comments.find(params[:comment_id]), :with => Entities::Comment |
13 | end | 16 | end |
14 | 17 | ||
15 | end | 18 | end |
@@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
1 | +module API | ||
2 | + module V1 | ||
3 | + class Users < Grape::API | ||
4 | + | ||
5 | + before { authenticate! } | ||
6 | + | ||
7 | + resource :users do | ||
8 | + | ||
9 | + #FIXME make the pagination | ||
10 | + #FIXME put it on environment context | ||
11 | +# get do | ||
12 | +# Users.all | ||
13 | +# end | ||
14 | + | ||
15 | + get ":id" do | ||
16 | + present Article.find(params[:id]).comments.find(params[:comment_id]), :with => Entities::User | ||
17 | + end | ||
18 | + | ||
19 | + # Create user. | ||
20 | + # | ||
21 | + # Parameters: | ||
22 | + # email (required) - Email | ||
23 | + # password (required) - Password | ||
24 | + # name - Name | ||
25 | + # Example Request: | ||
26 | + # POST /users | ||
27 | +# post do | ||
28 | + get do | ||
29 | +# authenticated_as_admin! | ||
30 | + required_attributes! [:email, :login, :password] | ||
31 | + attrs = attributes_for_keys [:email, :login, :password] | ||
32 | + user = User.new(attrs) | ||
33 | + if user.save | ||
34 | + present user, :with => Entities::User | ||
35 | + else | ||
36 | + not_found! | ||
37 | + end | ||
38 | + end | ||
39 | + end | ||
40 | + | ||
41 | + end | ||
42 | + end | ||
43 | +end |