tight-engine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 065ac37dc49f5b5e67ec4271bb92f71eee632e0f
4
+ data.tar.gz: 72dfcdf5eae3c379407c7c698293515e59822db8
5
+ SHA512:
6
+ metadata.gz: 50b1e579488cad7dc14f878f8e2605e8d1837500de6e980aa175f14839693e7027d5df4dd078ef2d86a085e1714ee0f94988976cb39ba56b547dfad6a8b00ea9
7
+ data.tar.gz: 0bc29fa1374faeb73b5949bc6f504f613fad53d39eac471350c69dfae7c6a78ae94e5429a6a927f3aba1eb4c6bb6255791c3112cd72c175f05f231448a13d3eb
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.rbc
2
+ *.sassc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Igor Bochkariov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ tight-engine
2
+ ============
3
+
4
+ A tight engine for a swift content management system
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ RAKE_ROOT = __FILE__
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'bundler/gem_tasks'
7
+ require 'minitest/autorun'
8
+
9
+ Rake::TestTask.new(:test) do |test|
10
+ test.libs << 'test'
11
+ test.test_files = Dir['test/**/test_*.rb']
12
+ test.verbose = true
13
+ end
14
+
15
+ task :default => :test
@@ -0,0 +1,3 @@
1
+ module Tight
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,148 @@
1
+ require 'tight-auth/permissions'
2
+
3
+ module Tight
4
+ ##
5
+ # Tight authorization module.
6
+ #
7
+ # @example
8
+ # class Nifty::Application < Tight::Application
9
+ # # optional settings
10
+ # set :credentials_reader, :visitor # the name of getter method in helpers
11
+ # # required statement
12
+ # register Tight::Access
13
+ # # example persistance storage
14
+ # enable :sessions
15
+ # end
16
+ #
17
+ # # optional helpers
18
+ # Nifty::Application.helpers do
19
+ # def visitor
20
+ # session[:visitor] ||= Visitor.guest_account
21
+ # end
22
+ # end
23
+ #
24
+ # # example visitor model
25
+ # module Visitor
26
+ # extend self
27
+ # def guest_account
28
+ # OpenStruct.new(:role => :guest, :id => 1)
29
+ # end
30
+ # end
31
+ #
32
+ # # example controllers
33
+ # Nifty::Application.controller :public_area do
34
+ # set_access :*
35
+ # get(:index){ 'public content' }
36
+ # end
37
+ # Nifty::Application.controller :members_area do
38
+ # set_access :member
39
+ # get(:index){ 'secret content' }
40
+ # end
41
+ # Nifty::Application.controller :login do
42
+ # set_access :*
43
+ # get(:index){ session[:visitor] = OpenStruct.new(:role => :guest, :id => 1) }
44
+ # end
45
+ #
46
+ module Access
47
+ class << self
48
+ def registered(app)
49
+ included(app)
50
+ app.default(:credentials_reader, :credentials)
51
+ app.default(:access_errors, true)
52
+ app.send :attr_reader, app.credentials_reader unless app.instance_methods.include?(app.credentials_reader)
53
+ app.set :permissions, Permissions.new
54
+ app.login_permissions if app.respond_to?(:login_permissions)
55
+ app.before do
56
+ authorized? || error(403, '403 Forbidden')
57
+ end
58
+ end
59
+
60
+ def included(base)
61
+ base.send(:include, InstanceMethods)
62
+ base.extend(ClassMethods)
63
+ end
64
+ end
65
+
66
+ module ClassMethods
67
+ ##
68
+ # Empties the list of permission.
69
+ #
70
+ def reset_access!
71
+ permissions.clear!
72
+ end
73
+
74
+ ##
75
+ # Allows access to action with objects.
76
+ #
77
+ # @example
78
+ # # in application
79
+ # set_access :*, :with => :login # allows everyone to interact with :login controller
80
+ # # in controller
81
+ # App.controller :members_area do
82
+ # set_access :member # allows all members to access :members_area controller
83
+ # end
84
+ #
85
+ def set_access(*args)
86
+ options = args.extract_options!
87
+ options[:object] ||= Array(@_controller).first.to_s.singularize.to_sym if @_controller.present?
88
+ permissions.add(*args, options)
89
+ end
90
+ end
91
+
92
+ module InstanceMethods
93
+ ##
94
+ # Checks if current visitor has access to current action with current controller.
95
+ #
96
+ def authorized?
97
+ access_action?
98
+ end
99
+
100
+ ##
101
+ # Returns current visitor.
102
+ #
103
+ def access_subject
104
+ send settings.credentials_reader
105
+ end
106
+
107
+ ##
108
+ # Checks if current visitor is one of the specified roles. Can accept a block.
109
+ #
110
+ def access_role?(*roles, &block)
111
+ settings.permissions.check(access_subject, :have => roles, &block)
112
+ end
113
+
114
+ ##
115
+ # Checks if current visitor is allowed to to the action with object. Can accept a block.
116
+ #
117
+ def access_action?(action = nil, object = nil, &block)
118
+ return true if response.status/100 == 4 && settings.access_errors
119
+ if respond_to?(:request) && action.nil? && object.nil?
120
+ object = request.controller
121
+ action = request.action
122
+ if object.nil? && action.present? && action.to_s.index('/')
123
+ object, action = request.env['PATH_INFO'].to_s.scan(/\/([^\/]*)/).map(&:first)
124
+ end
125
+ object ||= :''
126
+ action ||= :index
127
+ object = object.to_sym
128
+ action = action.to_sym
129
+ end
130
+ settings.permissions.check(access_subject, :allow => action, :with => object, &block)
131
+ end
132
+
133
+ ##
134
+ # Check if current visitor is allowed to interact with object by action. Can accept a block.
135
+ #
136
+ def access_object?(object = nil, action = nil, &block)
137
+ allow_action action, object, &block
138
+ end
139
+
140
+ ##
141
+ # Populates the list of objects the current visitor is allowed to interact with.
142
+ #
143
+ def access_objects(subject = access_subject, action = nil)
144
+ settings.permissions.find_objects(subject, action)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,20 @@
1
+ module Tight
2
+ module Login
3
+ module Controller
4
+ def self.included(base)
5
+ base.get :index do
6
+ render :slim, :"new", :layout => :"layout", :views => File.dirname(__FILE__)
7
+ end
8
+ base.post :index do
9
+ if authenticate
10
+ restore_location
11
+ else
12
+ params.delete 'password'
13
+ flash.now[:error] = 'Wrong password'
14
+ render :slim, :"new", :layout => :"layout", :views => File.dirname(__FILE__)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ doctype html
2
+ html
3
+ head
4
+ meta charset="utf-8"
5
+ meta name="robots" content="noindex"
6
+ title Padrino::Login
7
+ link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css"
8
+ body
9
+ .container.login style='width: 287px'
10
+ = yield
@@ -0,0 +1,37 @@
1
+ h3
2
+ | Login
3
+ br
4
+ small
5
+ a href=url('/') = request.env['HTTP_HOST']
6
+
7
+ form.form-horizontal.well action=''
8
+ - [:error, :warning, :success, :notice].each do |type|
9
+ - next if flash[type].blank?
10
+ .alert.alert-message class=('alert-' + (type == :notice ? :info : type).to_s) data-alert=true
11
+ = flash[type]
12
+
13
+ legend Social
14
+ .control-group
15
+ a href=url('/oauth/google')
16
+ img src='/images/social/google.png'
17
+
18
+ legend Obsolete
19
+ .control-group
20
+ .input-prepend
21
+ span.add-on
22
+ i.icon-envelope
23
+ input type=:text name=:email value=params[:email] placeholder='email'
24
+ .control-group
25
+ .input-prepend
26
+ span.add-on
27
+ i.icon-lock
28
+ input type=:password name=:password value=params[:password] placeholder='password'
29
+ .control-group
30
+ input.btn.btn-primary.pull-right type=:submit Log in
31
+ - if settings.login_bypass
32
+ label.checkbox
33
+ | Bypass
34
+ input type=:checkbox name=:bypass value='Bypass'
35
+
36
+ small
37
+ a href=url('/login/reset_password') Forgot password?
@@ -0,0 +1,138 @@
1
+ require 'tight-auth/login/controller'
2
+
3
+ module Tight
4
+ ##
5
+ # Tight authentication module.
6
+ #
7
+ # @example
8
+ # class Nifty::Application < Tight::Application
9
+ # # optional settings
10
+ # set :session_key, "visitor_id" # visitor key name in session storage, defaults to "_login_#{app.app_name}")
11
+ # set :login_model, :visitor # model name for visitor storage, defaults to :account, must be constantizable
12
+ # set :credentials_accessor, :visitor # the name of setter/getter method in helpers, defaults to :credentials
13
+ # enable :login_bypass # enables or disables login bypass in development mode, defaults to disable
14
+ # set :login_url, '/sign/in' # sets the utl to be redirected to if not logged in and in restricted area, defaults to '/login'
15
+ # disable :login_permissions # sets initial login permissions, defaults to { set_access(:*, :allow => :*, :with => :login) }
16
+ # disable :login_controller # disables default login controller to show an example of the custom one
17
+ #
18
+ # # required statement
19
+ # register Tight::Login
20
+ # # example persistance storage
21
+ # enable :sessions
22
+ # end
23
+ #
24
+ # TODO: example controllers
25
+ #
26
+ module Login
27
+ class << self
28
+ def registered(app)
29
+ warn 'Tight::Login must be registered before Tight::Access' if app.respond_to?(:set_access)
30
+ included(app)
31
+ setup_storage(app)
32
+ setup_controller(app)
33
+ app.before do
34
+ log_in if authorization_required?
35
+ end
36
+ end
37
+
38
+ def included(base)
39
+ base.send(:include, InstanceMethods)
40
+ end
41
+
42
+ private
43
+
44
+ def setup_storage(app)
45
+ app.default(:session_key, "_login_#{app.app_name}")
46
+ app.default(:login_model, :account)
47
+ app.default(:credentials_accessor, :credentials)
48
+ app.send :attr_reader, app.credentials_accessor unless app.instance_methods.include?(app.credentials_accessor)
49
+ app.send :attr_writer, app.credentials_accessor unless app.instance_methods.include?(:"#{app.credentials_accessor}=")
50
+ app.default(:login_bypass, false)
51
+ end
52
+
53
+ def setup_controller(app)
54
+ app.default(:login_url, '/login')
55
+ app.default(:login_permissions) { set_access(:*, :allow => :*, :with => :login) }
56
+ app.default(:login_controller, true)
57
+ app.controller(:login) { include Controller } if app.login_controller
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ # Returns the model used to authenticate visitors.
63
+ def login_model
64
+ @login_model ||= settings.login_model.to_s.classify.constantize
65
+ end
66
+
67
+ # Authenticates the visitor.
68
+ def authenticate
69
+ resource = login_model.authenticate(:email => params[:email], :password => params[:password])
70
+ resource ||= login_model.authenticate(:bypass => true) if settings.login_bypass && params[:bypass]
71
+ save_credentials(resource)
72
+ end
73
+
74
+ # Checks if the visitor is authenticated.
75
+ def logged_in?
76
+ !!(send(settings.credentials_accessor) || restore_credentials)
77
+ end
78
+
79
+ # Looks for authorization routine and calls it to check if the visitor is authorized.
80
+ def unauthorized?
81
+ respond_to?(:authorized?) && !authorized?
82
+ end
83
+
84
+ # Checks if the current location needs the visitor to be authorized.
85
+ def authorization_required?
86
+ if logged_in?
87
+ if unauthorized?
88
+ # 403 Forbidden, provided credentials were successfully
89
+ # authenticated but the credentials still do not grant
90
+ # the client permission to access the resource
91
+ error 403, '403 Forbidden'
92
+ else
93
+ false
94
+ end
95
+ else
96
+ unauthorized?
97
+ end
98
+ end
99
+
100
+ # Logs the visitor in using redirect to login page url.
101
+ def log_in
102
+ login_url = settings.login_url
103
+ if request.env['PATH_INFO'] != login_url
104
+ save_location
105
+ # 302 Found
106
+ redirect url(login_url)
107
+ # 401 Unauthorized, authentication is required and
108
+ # has not yet been provided
109
+ error 401, '401 Unauthorized'
110
+ end
111
+ end
112
+
113
+ # Saves credentials in session.
114
+ def save_credentials(resource)
115
+ session[settings.session_key] = resource.respond_to?(:id) ? resource.id : resource
116
+ send(:"#{settings.credentials_accessor}=", resource)
117
+ end
118
+
119
+ # Restores credentials from session using visitor model.
120
+ def restore_credentials
121
+ resource = login_model.authenticate(:id => session[settings.session_key])
122
+ send(:"#{settings.credentials_accessor}=", resource)
123
+ end
124
+
125
+ # Redirects back to saved location or '/'
126
+ def restore_location
127
+ redirect session.delete(:return_to) || url('/')
128
+ end
129
+
130
+ # Saves location to session for following redirect in case of successful authentication.
131
+ def save_location
132
+ uri = env['REQUEST_URI'] || url(env['PATH_INFO'])
133
+ return if uri.blank? || uri.match(/\.css$|\.js$|\.png$/)
134
+ session[:return_to] = "#{ENV['RACK_BASE_URI']}#{uri}"
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,180 @@
1
+ module Tight
2
+ ##
3
+ # Class to store and check permissions used in Padrino::Access.
4
+ #
5
+ class Permissions
6
+ ##
7
+ # Initializes new permissions storage.
8
+ #
9
+ # @example
10
+ # permissions = Permissions.new
11
+ #
12
+ def initialize
13
+ clear!
14
+ end
15
+
16
+ ##
17
+ # Clears permit records and action cache.
18
+ #
19
+ # @example
20
+ # permissions.clear!
21
+ #
22
+ def clear!
23
+ @permits = {}
24
+ @actions = {}
25
+ end
26
+
27
+ ##
28
+ # Adds a permission record to storage.
29
+ #
30
+ # @param [Symbol || Object] subject
31
+ # permit subject
32
+ # @param [Hash] options
33
+ # permit attributes
34
+ # @param [Symbol] options[:allow] || options[:action]
35
+ # what action to allow with objects
36
+ # @param [Symbol] options[:with] || options[:object]
37
+ # with what objects allow specified action
38
+ #
39
+ # @example
40
+ # permissions.add :robots, :allow => :protect, :object => :humans
41
+ # permissions.add @bender, :allow => :kill, :object => :humans
42
+ #
43
+ def add(*args)
44
+ @actions = {}
45
+ options = args.extract_options!
46
+ action, object = action_and_object(options)
47
+ object_type = detect_type(object)
48
+ args.each{ |subject| merge(subject, action, object_type) }
49
+ end
50
+
51
+ ##
52
+ # Checks if permission record exists. Returns a boolean or yield a block.
53
+ #
54
+ # @param [Object] subject
55
+ # performer of an action
56
+ # @param [Hash] options
57
+ # attributes to check
58
+ # @param [Symbol] options[:have]
59
+ # check if the subject has a role
60
+ # @param [Symbol] options[:allow] || options[:action]
61
+ # check if the subject is allowed to perform the action
62
+ # @param [Symbol] options[:with] || options[:object]
63
+ # check if the subject is allowed to interact with the subject
64
+ # @param [Proc]
65
+ # optional block to yield if the action is allowed
66
+ #
67
+ # @example
68
+ # # check if @bender have role :robots
69
+ # permissions.check @bender, :have => :robots # => true
70
+ # # check if @bender is allowed to kill :humans
71
+ # permissions.check @bender, :allow => :kill, :object => :humans # => true
72
+ # # check if @bender is allowed to kill :humans and yield a block
73
+ # permissions.check @bender, :allow => :kill, :object => :humans do
74
+ # @bender.kill_all! :humans
75
+ # end
76
+ #
77
+ def check(subject, options)
78
+ case
79
+ when options[:have]
80
+ check_role(subject, options[:have])
81
+ else
82
+ check_action(subject, *action_and_object(options))
83
+ end && (block_given? ? yield : true)
84
+ end
85
+
86
+ ##
87
+ # Populates and returns the list of objects available to the subject.
88
+ #
89
+ # @param [Object] subject
90
+ # the subject to be checked for actions
91
+ #
92
+ def find_objects(subject, target_action=nil)
93
+ find_actions(subject).inject([]) do |all,(action,objects)|
94
+ all |= objects if target_action.nil? || action == target_action || action == :*
95
+ all
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # Merges a list of new permits into permissions storage.
102
+ def merge(subject, actions, object_type)
103
+ subject_id = detect_id(subject)
104
+ @permits[subject_id] ||= {}
105
+ Array(actions).each do |action|
106
+ @permits[subject_id][action] ||= []
107
+ @permits[subject_id][action] |= [object_type]
108
+ end
109
+ end
110
+
111
+ # Checks if the subject has the role.
112
+ def check_role(subject, roles)
113
+ if subject.respond_to?(:role)
114
+ Array(roles).include?(subject.role)
115
+ else
116
+ false
117
+ end
118
+ end
119
+
120
+ # Checks if the subject is allowed to perform the action with the object.
121
+ def check_action(subject, action, object)
122
+ actions = find_actions(subject)
123
+ objects = actions && (Array(actions[action]) | Array(actions[:*]))
124
+ objects && (objects & [:*, detect_type(object)]).any?
125
+ end
126
+
127
+ # Finds all permits for the subject. Caches the permits in @actions.
128
+ # find_actions(@bender) # => { :kill => { :humans }, :drink => { :booze }, :* => { :login } }
129
+ def find_actions(subject)
130
+ subject_id = detect_id(subject)
131
+ return @actions[subject_id] if @actions[subject_id]
132
+ actions = @permits[subject_id] || {}
133
+ if subject.respond_to?(:role) && (role_actions = @permits[subject.role.to_sym])
134
+ actions.merge!(role_actions){ |_,left,right| Array(left)|Array(right) }
135
+ end
136
+ if public_actions = @permits[:*]
137
+ actions.merge!(public_actions){ |_,left,right| Array(left)|Array(right) }
138
+ end
139
+ @actions[subject_id] = actions
140
+ end
141
+
142
+ # Returns object type.
143
+ # detect_type :humans # => :human
144
+ # detect_type 'foobar' # => 'foobar'
145
+ def detect_type(object)
146
+ case object
147
+ when Symbol
148
+ object.to_s.singularize.to_sym
149
+ else
150
+ object
151
+ end
152
+ end
153
+
154
+ # Returns parametrized subject.
155
+ # detect_id :robots # => :robots
156
+ # detect_id sluggable_ar_resource # => 'Sluggable-resource-slug'
157
+ # detect_id some_resource_with_id # => '4'
158
+ # detect_id generic_object # => "<Object:0x00001234>"
159
+ def detect_id(subject)
160
+ case
161
+ when Symbol === subject
162
+ subject
163
+ when subject.respond_to?(:to_param)
164
+ subject.to_param
165
+ when subject.respond_to?(:id)
166
+ subject.id.to_s
167
+ else
168
+ "#{subject}"
169
+ end
170
+ end
171
+
172
+ # Utility function to extract action and object from options. Defaults to [:*, :*]
173
+ # action_and_object(:allow => :kill, :object => :humans) # => [:kill, :humans]
174
+ # action_and_object(:action => :romance, :with => :mutants) # => [:romance, :mutants]
175
+ # action_and_object({}) # => [:*, :*]
176
+ def action_and_object(options)
177
+ [options[:allow] || options[:action] || :*, options[:with] || options[:object] || :*]
178
+ end
179
+ end
180
+ end
data/lib/tight-auth.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'sinatra/base'
2
+
3
+ class Sinatra::Base
4
+ def self.default(option, *args, &block)
5
+ set(option, *args, &block) unless respond_to?(option)
6
+ end
7
+ end
8
+
9
+ require 'tight-auth/access'
10
+ require 'tight-auth/login'
@@ -0,0 +1,83 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'padrino-core'
4
+ require 'tight-auth'
5
+ require 'minitest/autorun'
6
+ require 'rack/test'
7
+
8
+ module TightLogger
9
+ attr_accessor :io
10
+ def self.io
11
+ @io ||= StringIO.new
12
+ end
13
+ end
14
+
15
+ Padrino::Logger::Config[:test] = { :log_level => :devel, :stream => TightLogger.io }
16
+
17
+ class Minitest::Spec
18
+ include Rack::Test::Methods
19
+
20
+ def mock_app(base=Padrino::Application, &block)
21
+ @app = Sinatra.new(base, &block)
22
+ end
23
+
24
+ def app
25
+ Rack::Lint.new(@app)
26
+ end
27
+
28
+ def set_access(*args)
29
+ @app.set_access(*args)
30
+ end
31
+
32
+ def allow(subject = nil, path = '/')
33
+ @app.fake_session[:visitor] = nil
34
+ get "/login/#{subject.id}" if subject
35
+ get path
36
+ assert_equal 200, status, caller.first.to_s
37
+ end
38
+
39
+ def deny(subject = nil, path = '/')
40
+ @app.fake_session[:visitor] = nil
41
+ get "/login/#{subject.id}" if subject
42
+ get path
43
+ assert_equal 403, status, caller.first.to_s
44
+ end
45
+
46
+ def status
47
+ response.status
48
+ end
49
+
50
+ def body
51
+ response.body
52
+ end
53
+
54
+ def response
55
+ last_response
56
+ end
57
+ end
58
+
59
+ module Character
60
+ extend self
61
+
62
+ def authenticate(credentials)
63
+ case
64
+ when credentials[:email] && credentials[:password]
65
+ target = all.find{ |resource| resource.id.to_s == credentials[:email] }
66
+ target.name.gsub(/[^A-Z]/,'') == credentials[:password] ? target : nil
67
+ when credentials.has_key?(:id)
68
+ all.find{ |resource| resource.id == credentials[:id] }
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def all
75
+ @all = [
76
+ OpenStruct.new(:id => :bender, :name => 'Bender Bending Rodriguez', :role => :robots ),
77
+ OpenStruct.new(:id => :leela, :name => 'Turanga Leela', :role => :mutants ),
78
+ OpenStruct.new(:id => :fry, :name => 'Philip J. Fry', :role => :humans ),
79
+ OpenStruct.new(:id => :ami, :name => 'Amy Wong', :role => :humans ),
80
+ OpenStruct.new(:id => :zoidberg, :name => 'Dr. John A. Zoidberg', :role => :lobsters),
81
+ ]
82
+ end
83
+ end
@@ -0,0 +1,124 @@
1
+ require File.expand_path('../auth_helper', __FILE__)
2
+
3
+ describe "Tight::Access" do
4
+ before do
5
+ mock_app do
6
+ set :credentials_reader, :visitor
7
+ register Tight::Access
8
+ set_access :*, :allow => :login
9
+ set :users, Character.all
10
+ get(:login, :with => :id) do
11
+ user = settings.users.find{ |user| user.id.to_s == params[:id] }
12
+ self.send(:"#{settings.credentials_reader}=", user)
13
+ end
14
+ get(:index){ 'foo' }
15
+ get(:bend){ 'bend' }
16
+ get(:drink){ 'bend' }
17
+ get(:subject){ self.send(settings.credentials_reader).inspect }
18
+ get(:stop_partying){ 'stop partying' }
19
+ controller :surface do
20
+ get(:live) { 'live on the surface' }
21
+ end
22
+ controller :sewers do
23
+ get(:live) { 'live in the sewers' }
24
+ get(:visit) { 'visit the sewers' }
25
+ end
26
+ set :fake_session, {}
27
+ helpers do
28
+ def visitor
29
+ settings.fake_session[:visitor]
30
+ end
31
+ def visitor=(user)
32
+ settings.fake_session[:visitor] = user
33
+ end
34
+ end
35
+ end
36
+ Character.all.each do |user|
37
+ instance_variable_set :"@#{user.id}", user
38
+ end
39
+ end
40
+
41
+ it 'should register with authorization module' do
42
+ assert @app.respond_to? :set_access
43
+ assert_kind_of Tight::Permissions, @app.permissions
44
+ end
45
+
46
+ it 'should properly detect access subject' do
47
+ set_access :*
48
+ get '/login/ami'
49
+ get '/subject'
50
+ assert_equal @ami.inspect, body
51
+ end
52
+
53
+ it 'should reset access properly' do
54
+ set_access :*
55
+ allow
56
+ @app.reset_access!
57
+ deny
58
+ end
59
+
60
+ it 'should set group access' do
61
+ # only humans should be allowed on TV
62
+ set_access :humans
63
+ allow @fry
64
+ deny @bender
65
+ end
66
+
67
+ it 'should set individual access' do
68
+ # only Fry should be allowed to romance Leela
69
+ set_access @fry
70
+ allow @fry
71
+ deny @ami
72
+ end
73
+
74
+ it 'should set mixed individual and group access' do
75
+ # only humans and Leela should be allowed on the surface
76
+ set_access :humans
77
+ set_access @leela
78
+ allow @fry
79
+ allow @leela
80
+ end
81
+
82
+ it 'should set action-specific access' do
83
+ # bender should be allowed to bend, and he's denied to stop partying
84
+ set_access @bender, :allow => :bend
85
+ set_access @fry, :allow => :stop_partying
86
+ allow @bender, '/bend'
87
+ deny @bender, '/stop_partying'
88
+ allow @fry, '/stop_partying'
89
+ deny @fry, '/bend'
90
+ end
91
+
92
+ it 'should set multiple action' do
93
+ # bender should be allowed to bend and drink
94
+ set_access @bender, :allow => [:drink, :bend]
95
+ allow @bender, '/drink'
96
+ allow @bender, '/bend'
97
+ end
98
+
99
+ it 'should set object-specific access' do
100
+ # only humans and Leela should be allowed to live on the surface
101
+ # only mutants should be allowed to live in the sewers though humans can visit
102
+ set_access :humans, :allow => :live, :with => :surface
103
+ set_access :mutants, :allow => :live, :with => :sewers
104
+ set_access @leela, :allow => :live, :with => :surface
105
+ set_access :humans, :allow => :visit, :with => :sewers
106
+ allow @fry, '/surface/live'
107
+ deny @fry, '/sewers/live'
108
+ allow @fry, '/sewers/visit'
109
+ allow @leela, '/surface/live'
110
+ allow @leela, '/sewers/live'
111
+ end
112
+
113
+ it 'should detect object when setting access from controller' do
114
+ # only humans and lobsters should have binocular vision
115
+ @app.controller :binocular do
116
+ set_access :humans, :lobsters
117
+ get(:vision) { 'binocular vision' }
118
+ end
119
+ deny @fry, '/'
120
+ allow @fry, '/binocular/vision'
121
+ allow @zoidberg, '/binocular/vision'
122
+ deny @leela, '/binocular/vision'
123
+ end
124
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path('../auth_helper', __FILE__)
2
+
3
+ Account = Character
4
+
5
+ describe "Tight::Auth" do
6
+ before do
7
+ mock_app do
8
+ enable :sessions
9
+ register Tight::Login
10
+ register Tight::Access
11
+ get(:robot_area){ 'robot_area' }
12
+ set_access :robots, :allow => :robot_area
13
+ end
14
+ end
15
+
16
+ it 'should login and access play nicely together' do
17
+ get '/robot_area'
18
+ assert_equal 302, status
19
+
20
+ post '/login', :email => :bender, :password => 'BBR'
21
+ get '/robot_area'
22
+ assert_equal 200, status
23
+
24
+ post '/login', :email => :leela, :password => 'TL'
25
+ get '/robot_area'
26
+ assert_equal 403, status
27
+ end
28
+
29
+ it 'should whine if the order is wrong' do
30
+ out, err = capture_io do
31
+ mock_app do
32
+ register Tight::Access
33
+ register Tight::Login
34
+ end
35
+ end
36
+ assert_match /must be registered before/, err
37
+ end
38
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path('../auth_helper', __FILE__)
2
+ require 'padrino-helpers'
3
+
4
+ describe "Tight::Access" do
5
+ before do
6
+ mock_app do
7
+ set :credentials_accessor, :visitor
8
+ set :login_model, :character
9
+ enable :sessions
10
+ register Tight::Login
11
+ get(:index){ 'index' }
12
+ get(:restricted){ 'secret' }
13
+ helpers do
14
+ def authorized?
15
+ return !['/restricted'].include?(request.env['PATH_INFO']) unless visitor
16
+ case
17
+ when visitor.id == :bender
18
+ true
19
+ else
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
25
+ Character.all.each do |user|
26
+ instance_variable_set :"@#{user.id}", user
27
+ end
28
+ end
29
+
30
+ it 'should pass unrestricted area' do
31
+ get '/'
32
+ assert_equal 200, status
33
+ end
34
+
35
+ it 'should be redirected from restricted area to login page' do
36
+ get '/restricted'
37
+ assert_equal 302, status
38
+ get response.location
39
+ assert_equal 200, status
40
+ assert_match /<form .*<input .*/, body
41
+ end
42
+
43
+ it 'should not be able to authenticate with wrong password' do
44
+ post '/login', :email => :bender, :password => '123'
45
+ assert_equal 200, status
46
+ assert_match 'Wrong password', body
47
+ end
48
+
49
+ it 'should be able to authenticate with email and password' do
50
+ post '/login', :email => :bender, :password => 'BBR'
51
+ assert_equal 302, status
52
+ end
53
+
54
+ it 'should be redirected back' do
55
+ get '/restricted'
56
+ post response.location, :email => :bender, :password => 'BBR'
57
+ assert_match /\/restricted$/, response.location
58
+ end
59
+
60
+ it 'should be redirected to root if no location was saved' do
61
+ post '/login', :email => :bender, :password => 'BBR'
62
+ assert_match /\/$/, response.location
63
+ end
64
+
65
+ it 'should be allowed in restricted area after logging in' do
66
+ post '/login', :email => :bender, :password => 'BBR'
67
+ get '/restricted'
68
+ assert_equal 'secret', body
69
+ end
70
+
71
+ it 'should not be allowed in restricted area after logging in an account lacking privileges' do
72
+ post '/login', :email => :leela, :password => 'TL'
73
+ get '/restricted'
74
+ assert_equal 403, status
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+ require 'tight/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'tight-engine'
6
+ spec.version = Tight::VERSION
7
+ spec.description = 'Tight engine for Swift CMS'
8
+ spec.summary = 'A tight engine for a swift content management system'
9
+
10
+ spec.authors = ['Igor Bochkariov']
11
+ spec.email = ['ujifgc@gmail.com']
12
+ spec.homepage = 'https://github.com/ujifgc/tight-engine'
13
+ spec.license = 'MIT'
14
+
15
+ spec.require_paths = ['lib']
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.test_files = spec.files.grep(%r{^test/})
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.3'
20
+ spec.add_development_dependency 'rake'
21
+ spec.add_development_dependency 'minitest'
22
+ spec.add_development_dependency 'padrino-core'
23
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tight-engine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Igor Bochkariov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: padrino-core
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Tight engine for Swift CMS
70
+ email:
71
+ - ujifgc@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - LICENSE
78
+ - README.md
79
+ - Rakefile
80
+ - lib/tight-auth.rb
81
+ - lib/tight-auth/access.rb
82
+ - lib/tight-auth/login.rb
83
+ - lib/tight-auth/login/controller.rb
84
+ - lib/tight-auth/login/layout.slim
85
+ - lib/tight-auth/login/new.slim
86
+ - lib/tight-auth/permissions.rb
87
+ - lib/tight/version.rb
88
+ - test/auth_helper.rb
89
+ - test/test_padrino_access.rb
90
+ - test/test_padrino_auth.rb
91
+ - test/test_padrino_login.rb
92
+ - tight-engine.gemspec
93
+ homepage: https://github.com/ujifgc/tight-engine
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.2.2
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: A tight engine for a swift content management system
117
+ test_files:
118
+ - test/auth_helper.rb
119
+ - test/test_padrino_access.rb
120
+ - test/test_padrino_auth.rb
121
+ - test/test_padrino_login.rb
122
+ has_rdoc: