tight-engine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: