wcc-auth 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +8 -0
- data/lib/omniauth/strategies/watermark.rb +29 -0
- data/lib/wcc/auth/ability.rb +36 -0
- data/lib/wcc/auth/access_level.rb +38 -0
- data/lib/wcc/auth/config.rb +77 -0
- data/lib/wcc/auth/controller_helpers.rb +11 -0
- data/lib/wcc/auth/devise/watermark_callbacks_controller.rb +26 -0
- data/lib/wcc/auth/devise.rb +22 -0
- data/lib/wcc/auth/providers/active_record.rb +41 -0
- data/lib/wcc/auth/providers.rb +8 -0
- data/lib/wcc/auth/version.rb +5 -0
- data/lib/wcc/auth.rb +33 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/wcc/auth/access_level_spec.rb +65 -0
- data/spec/wcc/auth/config_spec.rb +21 -0
- data/spec/wcc/auth/tiered_ability_spec.rb +89 -0
- data/spec/wcc_auth_spec.rb +33 -0
- data/wcc-auth.gemspec +29 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c2afec20c4229b28f32abae4da9d22644c57bb2
|
4
|
+
data.tar.gz: 84abb21d431971dd9ce00576fac0f7aa5b5411d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b64cd3fccbe65635e39dd47a8e162ec073c960611f2c566ce8db7da513dbdc86a18bbb04427fa14c38f2b1ef8865d30a173a343db1f8a25a19453d5ec710ef8b
|
7
|
+
data.tar.gz: 5a86a83daf93ec913e5c00bd6fbbf771cedd3357237a7c05d928521ed02c8b28afab82e277fe9d53581c167c95ab4e574e001c8757bb71bbf9274493fb0e2ff1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Watermark Community Church, Dallas, TX
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all 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,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# WCC::Auth
|
2
|
+
|
3
|
+
Provides the necessary tools for handling authentication through
|
4
|
+
Watermark's OAuth provider as well as authorizing the user has access to
|
5
|
+
specific features within the application. There are special hooks for
|
6
|
+
Rails apps using Devise, but the primitive structures could be used on
|
7
|
+
any Ruby project. Currently, the only tested path is Rails with Devise.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'wcc-auth', '~> 0.3.0'
|
15
|
+
```
|
16
|
+
|
17
|
+
If you are using a Rails app with Devise you can use a special require
|
18
|
+
hook that will setup all the Devise specific configuration for you.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'wcc-auth', '~> 0.3.0', require: 'wcc/auth/devise'
|
22
|
+
```
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
There are a few steps to setup your app. These instructions are specific
|
27
|
+
to a Rails app.
|
28
|
+
|
29
|
+
#### Add the configuration block to an initializer
|
30
|
+
|
31
|
+
In order to configure the gem you must run the `WCC::Auth.setup` block.
|
32
|
+
See below for an example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
WCC::Auth.setup do |config|
|
36
|
+
config.app_name = "app-name"
|
37
|
+
config.environment = Rails.env
|
38
|
+
config.app_id = 'app-client-id-from-oauth-provider'
|
39
|
+
config.app_secret = 'app-client-secret-from-oauth-provider'
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Setup your controllers
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# Add this include to your ApplicationController
|
47
|
+
class ApplicationController < ActionController::Base
|
48
|
+
include WCC::Auth::ControllerHelpers
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
#### Setup your user model
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class User < ActiveRecord::Base
|
56
|
+
include WCC::Auth::Providers::ActiveRecord
|
57
|
+
|
58
|
+
# ...
|
59
|
+
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
#### Setup authorization (optional)
|
64
|
+
|
65
|
+
If you would like to use the `TieredAbility` class included with
|
66
|
+
`WCC::Auth` just create an Ability class that extends the
|
67
|
+
`WCC::Auth::TieredAbility` class. The authenticated user will include an
|
68
|
+
info variables called `access_level_id`. This corresponds to a
|
69
|
+
`WCC::Auth::AccessLevel`.
|
70
|
+
|
71
|
+
The access levels are broken down into 5 tiers with the following rules:
|
72
|
+
|
73
|
+
* **No access** -- This is the default level
|
74
|
+
* **Basic** -- This is provides read-only access
|
75
|
+
* **Contribute** -- Read-write for only data the user owns
|
76
|
+
* **Manage** -- Read-write for other's data
|
77
|
+
* **App Admin** -- Can change app configuration
|
78
|
+
* **System Admin** -- Has full access to all features always
|
79
|
+
|
80
|
+
Each tier inherits all priveleges of the lower tiers. The rules here are
|
81
|
+
guidelines for the app to follow. It is ultimately up to the client
|
82
|
+
application to decide what each of these tiers means for it. Do your
|
83
|
+
best to adhere to these rules.
|
84
|
+
|
85
|
+
Here is an example Ability class using the DSL provided by `WCC::Auth`.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class Ability < WCC::Auth::TieredAbility
|
89
|
+
at_level(:contribute) do |user|
|
90
|
+
can :read, Person
|
91
|
+
can :manage, Task, created_by_id: user.id
|
92
|
+
can :manage, Comment, created_by_id: user.id
|
93
|
+
|
94
|
+
cannot :destroy, Task
|
95
|
+
end
|
96
|
+
|
97
|
+
at_level(:appadmin) do |user|
|
98
|
+
can :manage, :all
|
99
|
+
cannot :create, TaskGroup
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
at_level(:sysadmin) do |user|
|
104
|
+
can :manage, :all
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
1. Fork it
|
112
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
113
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
114
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
115
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class OmniAuth::Strategies::Watermark < OmniAuth::Strategies::OAuth2
|
2
|
+
option :name, :watermark
|
3
|
+
|
4
|
+
option :client_options, {
|
5
|
+
site: WCC::Auth.config.authorize_site,
|
6
|
+
authorize_path: WCC::Auth.config.authorize_path,
|
7
|
+
}
|
8
|
+
|
9
|
+
option :authorize_params, WCC::Auth.config.authorize_params
|
10
|
+
|
11
|
+
uid do
|
12
|
+
raw_info["id"]
|
13
|
+
end
|
14
|
+
|
15
|
+
info do
|
16
|
+
{
|
17
|
+
email: raw_info["email"],
|
18
|
+
first_name: raw_info["first_name"],
|
19
|
+
last_name: raw_info["last_name"],
|
20
|
+
access_level_id: raw_info["access_level_id"],
|
21
|
+
arena_id: raw_info["arena_id"],
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def raw_info
|
26
|
+
@raw_info ||= access_token.get('/api/v1/me.json').parsed
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'cancan'
|
2
|
+
|
3
|
+
class WCC::Auth::TieredAbility
|
4
|
+
include CanCan::Ability
|
5
|
+
|
6
|
+
def initialize(user)
|
7
|
+
@user = user
|
8
|
+
grant_access_at(@user.access_level)
|
9
|
+
end
|
10
|
+
|
11
|
+
def grant_access_at(access_level)
|
12
|
+
levels.each do |level, can_blocks|
|
13
|
+
if access_level >= level
|
14
|
+
can_blocks.each { |can_block| instance_exec(@user, &can_block) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def levels
|
20
|
+
self.class.levels
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.at_level(level_key, &can_block)
|
24
|
+
level = WCC::Auth::AccessLevel[level_key] or
|
25
|
+
raise ArgumentError, "#{level_key} is not a valid access level"
|
26
|
+
levels[level] << can_block
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.levels
|
30
|
+
@levels ||= Hash.new { |hash, key| hash[key] = [] }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.inherited(subclass)
|
34
|
+
subclass.send :instance_variable_set, :@levels, levels.dup
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module WCC::Auth
|
2
|
+
|
3
|
+
ACCESS_LEVELS = [
|
4
|
+
{ id: nil, name: "None", slug: "none", description: "no access", level: 0 },
|
5
|
+
{ id: 1, name: "Basic", slug: "basic", description: "read access", level: 1 },
|
6
|
+
{ id: 2, name: "Contribute", slug: "contribute", description: "read-write of data user owns", level: 2 },
|
7
|
+
{ id: 3, name: "Manage", slug: "manage", description: "read-write of other's data", level: 3 },
|
8
|
+
{ id: 4, name: "App Admin", slug: "appadmin", description: "read-write app configuration", level: 4 },
|
9
|
+
{ id: 5, name: "System Admin", slug: "sysadmin", description: "full access", level: 5 },
|
10
|
+
]
|
11
|
+
|
12
|
+
AccessLevel = Struct.new(:id, :name, :slug, :description, :level) do
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
def <=>(record)
|
16
|
+
self.level <=> record.level
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.[](index_or_slug, db=ACCESS_LEVELS)
|
20
|
+
all(db).select do |al|
|
21
|
+
al.level.to_s === index_or_slug.to_s ||
|
22
|
+
al.slug === index_or_slug.to_s
|
23
|
+
end.last
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.all(db=ACCESS_LEVELS)
|
27
|
+
db.collect do |row|
|
28
|
+
new.tap { |access_level|
|
29
|
+
row.each { |field, value|
|
30
|
+
access_level[field] = value
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end.sort
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
WCC::Auth::Config = Struct.new(:environment,
|
2
|
+
:app_name,
|
3
|
+
:app_url,
|
4
|
+
:app_url_protocol,
|
5
|
+
:app_id,
|
6
|
+
:app_secret,
|
7
|
+
:app_domain_suffix,
|
8
|
+
:authorize_site,
|
9
|
+
:authorize_path,
|
10
|
+
:authorize_params) do
|
11
|
+
|
12
|
+
def authorize_site
|
13
|
+
self[:authorize_site] || default_authorize_site
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorize_path
|
17
|
+
self[:authorize_path] || "/oauth/authorize"
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorize_params
|
21
|
+
self[:authorize_params] || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def app_url
|
25
|
+
self[:app_url] || default_app_url
|
26
|
+
end
|
27
|
+
|
28
|
+
def app_url_protocol
|
29
|
+
self[:app_domain_suffix] || default_app_url_protocol
|
30
|
+
end
|
31
|
+
|
32
|
+
def app_domain_suffix
|
33
|
+
self[:app_domain_suffix] || default_app_domain_suffix
|
34
|
+
end
|
35
|
+
|
36
|
+
def nucleus_url
|
37
|
+
case environment.to_sym
|
38
|
+
when :production
|
39
|
+
"https://login.watermark.org"
|
40
|
+
when :staging
|
41
|
+
"http://login.staging.watermark.org"
|
42
|
+
when :development
|
43
|
+
"http://login.dev"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def default_app_url
|
50
|
+
"#{app_url_protocol}://#{app_name}#{app_domain_suffix}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_app_url_protocol
|
54
|
+
case environment.to_sym
|
55
|
+
when :production
|
56
|
+
"https"
|
57
|
+
else
|
58
|
+
"http"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_app_domain_suffix
|
63
|
+
case environment.to_sym
|
64
|
+
when :production
|
65
|
+
".watermark.org"
|
66
|
+
when :staging
|
67
|
+
".staging.watermark.org"
|
68
|
+
when :development
|
69
|
+
".dev"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_authorize_site
|
74
|
+
nucleus_url
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class WCC::Auth::Devise::WatermarkCallbacksController < Devise::OmniauthCallbacksController
|
2
|
+
skip_authorization_check if respond_to?(:skip_authorization_check)
|
3
|
+
|
4
|
+
def watermark
|
5
|
+
oauth_data = request.env["omniauth.auth"]
|
6
|
+
@user = User.initialize_from_watermark_oauth(oauth_data)
|
7
|
+
@user.save
|
8
|
+
|
9
|
+
sign_in_and_redirect @user
|
10
|
+
end
|
11
|
+
|
12
|
+
def failure
|
13
|
+
Rails.logger.error failed_strategy.name
|
14
|
+
Rails.logger.error failure_message
|
15
|
+
set_flash_message :alert, :failure, :kind => OmniAuth::Utils.camelize(failed_strategy.name), :reason => failure_message
|
16
|
+
redirect_to after_omniauth_failure_path_for(resource_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def after_omniauth_failure_path_for(scope)
|
22
|
+
root_path
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "wcc/auth"
|
2
|
+
require "devise"
|
3
|
+
|
4
|
+
module WCC
|
5
|
+
module Auth
|
6
|
+
module Devise
|
7
|
+
autoload :WatermarkCallbacksController, 'wcc/auth/devise/watermark_callbacks_controller'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
WCC::Auth.finalize_callbacks << -> {
|
13
|
+
require "omniauth/strategies/watermark"
|
14
|
+
Devise.setup do |config|
|
15
|
+
config.omniauth :watermark,
|
16
|
+
WCC::Auth.config.app_id,
|
17
|
+
WCC::Auth.config.app_secret
|
18
|
+
end
|
19
|
+
|
20
|
+
OmniAuth.config.full_host = WCC::Auth.config.app_url
|
21
|
+
}
|
22
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module WCC::Auth::Providers::ActiveRecord
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def credential_data_mapping(oauth_data)
|
6
|
+
{
|
7
|
+
email: oauth_data.info.email,
|
8
|
+
first_name: oauth_data.info.first_name,
|
9
|
+
last_name: oauth_data.info.last_name,
|
10
|
+
access_token: oauth_data.credentials.token,
|
11
|
+
access_level_id: oauth_data.info.access_level_id,
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_from_watermark_oauth(oauth_data)
|
16
|
+
find_or_initialize_by(provider: :watermark, uid: oauth_data.uid).tap do |user|
|
17
|
+
user.assign_attributes(credential_data_mapping(oauth_data))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
def access_level
|
26
|
+
WCC::Auth::AccessLevel[access_level_id || :none]
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_access?(level)
|
30
|
+
access_level >= WCC::Auth::AccessLevel[level]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.included(receiver)
|
36
|
+
receiver.send :extend, ClassMethods
|
37
|
+
receiver.send :include, InstanceMethods
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
data/lib/wcc/auth.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "wcc/auth/version"
|
2
|
+
require "wcc/auth/config"
|
3
|
+
|
4
|
+
require "wcc/auth/access_level"
|
5
|
+
require "wcc/auth/ability"
|
6
|
+
require "wcc/auth/providers"
|
7
|
+
require "wcc/auth/controller_helpers"
|
8
|
+
|
9
|
+
require "omniauth"
|
10
|
+
require "omniauth-oauth2"
|
11
|
+
|
12
|
+
module WCC
|
13
|
+
module Auth
|
14
|
+
|
15
|
+
def self.setup
|
16
|
+
yield config
|
17
|
+
finalize
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
@config ||= WCC::Auth::Config.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.finalize
|
25
|
+
finalize_callbacks.each(&:call)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.finalize_callbacks
|
29
|
+
@finalize_callbacks ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(__FILE__, "..", "..", "lib"))
|
2
|
+
|
3
|
+
require 'wcc/auth'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
|
10
|
+
config.order = 'random'
|
11
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WCC::Auth::AccessLevel do
|
4
|
+
let(:klass) { WCC::Auth::AccessLevel }
|
5
|
+
let(:test_db) do
|
6
|
+
[
|
7
|
+
{ id: 1, name: "Foo", slug: "foo", description: "foo", level: 2 },
|
8
|
+
{ id: 2, name: "Bar", slug: "bar", description: "bar", level: 1 },
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "class indexing operators" do
|
13
|
+
subject { klass }
|
14
|
+
|
15
|
+
it "returns record by slug if symbol or string is passed" do
|
16
|
+
expect(klass["foo", test_db].level).to eq(2)
|
17
|
+
expect(klass[:bar, test_db].level).to eq(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns record by level if number is passed" do
|
21
|
+
expect(klass[2, test_db].level).to eq(2)
|
22
|
+
expect(klass["1", test_db].level).to eq(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "uses ACCESS_LEVELS db by default" do
|
26
|
+
expect(klass[:appadmin].slug).to eq("appadmin")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "::all class method" do
|
31
|
+
subject { klass }
|
32
|
+
|
33
|
+
it "returns array of records as listed in db argument" do
|
34
|
+
levels = subject.all(test_db)
|
35
|
+
expect(levels.count).to eq(2)
|
36
|
+
expect(levels.all? { |level| level.kind_of?(klass) }).to be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns objects in level order" do
|
40
|
+
levels = subject.all(test_db)
|
41
|
+
expect(levels[0].slug).to eq("bar")
|
42
|
+
expect(levels[1].name).to eq("Foo")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "uses ACCESS_LEVELS constant as DB if non provided" do
|
46
|
+
levels = subject.all
|
47
|
+
expect(levels.count).to eq(WCC::Auth::ACCESS_LEVELS.count)
|
48
|
+
expect(levels.last.slug).to eq("sysadmin")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "comparability" do
|
53
|
+
it "implements comparable methods" do
|
54
|
+
a = klass.new
|
55
|
+
b = klass.new
|
56
|
+
a.level = 1
|
57
|
+
b.level = 2
|
58
|
+
expect(a < b).to be_true
|
59
|
+
expect(b == a).to be_false
|
60
|
+
expect(b < a).to be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WCC::Auth::Config do
|
4
|
+
let(:klass) { WCC::Auth::Config }
|
5
|
+
|
6
|
+
describe "default attributes" do
|
7
|
+
subject { klass.new }
|
8
|
+
it "sets authorize_site according to environment" do
|
9
|
+
subject.environment = :development
|
10
|
+
expect(subject.authorize_site).to eq("http://login.dev")
|
11
|
+
subject.environment = :production
|
12
|
+
expect(subject.authorize_site).to eq("https://login.watermark.org")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets authorize_path" do
|
16
|
+
expect(subject.authorize_path).to eq("/oauth/authorize")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WCC::Auth::TieredAbility do
|
4
|
+
let(:klass) { WCC::Auth::TieredAbility }
|
5
|
+
let(:access_level) { WCC::Auth::AccessLevel }
|
6
|
+
|
7
|
+
describe "defining levels" do
|
8
|
+
it "stores blocks in ::levels" do
|
9
|
+
subclass = Class.new(klass) do
|
10
|
+
at_level(:basic) {}
|
11
|
+
at_level(:contribute) {}
|
12
|
+
at_level(:basic) {}
|
13
|
+
end
|
14
|
+
expect(subclass.levels[access_level[:basic]].size).to eq(2)
|
15
|
+
expect(subclass.levels[access_level[:contribute]].size).to eq(1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#initialize" do
|
20
|
+
it "grants access to the user's access_level" do
|
21
|
+
ability = Class.new(klass) do
|
22
|
+
at_level(:basic) { |user| user.ping }
|
23
|
+
end
|
24
|
+
user = double("User")
|
25
|
+
level = double("AccessLevel")
|
26
|
+
allow(level).to receive(:>=).and_return(true)
|
27
|
+
expect(user).to receive(:access_level).and_return(level)
|
28
|
+
expect(user).to receive(:ping)
|
29
|
+
ability.new(user)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#grant_access_at" do
|
34
|
+
let(:mock_klass) {
|
35
|
+
Class.new(klass) do
|
36
|
+
def initialize(mock)
|
37
|
+
@user = mock
|
38
|
+
end
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
it "runs only the levels that the given access level allows" do
|
43
|
+
ability = Class.new(mock_klass) do
|
44
|
+
at_level(:basic) { |mock| mock.basic }
|
45
|
+
at_level(:appadmin) { |mock| mock.appadmin }
|
46
|
+
end
|
47
|
+
mock = double
|
48
|
+
expect(mock).to receive(:basic)
|
49
|
+
ability.new(mock).grant_access_at(access_level[:manage])
|
50
|
+
end
|
51
|
+
|
52
|
+
it "runs the blocks in the defined order" do
|
53
|
+
ability = Class.new(mock_klass) do
|
54
|
+
at_level(:appadmin) { |mock| mock.first }
|
55
|
+
at_level(:basic) { |mock| mock.second }
|
56
|
+
at_level(:manage) { |mock| mock.fourth }
|
57
|
+
at_level(:basic) { |mock| mock.third }
|
58
|
+
end
|
59
|
+
mock = double
|
60
|
+
expect(mock).to receive(:first).ordered
|
61
|
+
expect(mock).to receive(:second).ordered
|
62
|
+
expect(mock).to receive(:third).ordered
|
63
|
+
expect(mock).to receive(:fourth).ordered
|
64
|
+
ability.new(mock).grant_access_at(access_level[:sysadmin])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "subclassing" do
|
69
|
+
it "inherits defined levels from parent class" do
|
70
|
+
sub1 = Class.new(klass) do
|
71
|
+
at_level(:basic) {}
|
72
|
+
end
|
73
|
+
sub2 = Class.new(sub1)
|
74
|
+
expect(sub2.levels.size).to eq(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "subclass and parent class have independent levels" do
|
78
|
+
sub1 = Class.new(klass) do
|
79
|
+
at_level(:basic) {}
|
80
|
+
end
|
81
|
+
sub2 = Class.new(sub1) do
|
82
|
+
at_level(:contribute) {}
|
83
|
+
end
|
84
|
+
expect(sub1.levels.size).to eq(1)
|
85
|
+
expect(sub2.levels.size).to eq(2)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WCC::Auth do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@previous_config = WCC::Auth.send(:instance_variable_get, :@config)
|
7
|
+
@previous_callbacks = WCC::Auth.send(:instance_variable_get, :@finalize_callbacks)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
WCC::Auth.send :instance_variable_set, :@config, @previous_config
|
12
|
+
WCC::Auth.send :instance_variable_set, :@finalize_callbacks, @previous_callbacks
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "::setup class method" do
|
16
|
+
it "yields config with ::setup and sets it to ::config" do
|
17
|
+
the_config = nil
|
18
|
+
WCC::Auth.setup do |config|
|
19
|
+
expect(config).to be_a(WCC::Auth::Config)
|
20
|
+
the_config = config
|
21
|
+
end
|
22
|
+
expect(the_config).to eq(WCC::Auth.config)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "calls finalize" do
|
26
|
+
ping = nil
|
27
|
+
WCC::Auth.finalize_callbacks << -> { ping = "pong" }
|
28
|
+
WCC::Auth.setup { |config| }
|
29
|
+
expect(ping).to eq("pong")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/wcc-auth.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wcc/auth/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wcc-auth"
|
8
|
+
spec.version = WCC::Auth::VERSION
|
9
|
+
spec.authors = ["Travis Petticrew"]
|
10
|
+
spec.email = ["tpetticrew@watermark.org"]
|
11
|
+
spec.description = %q{Authentication / Authorization library for Watermark apps}
|
12
|
+
spec.summary = File.readlines("README.md").join
|
13
|
+
spec.homepage = "https://github.com/watermarkchurch/wcc-auth"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "cancancan", "~> 1.9.2"
|
22
|
+
spec.add_dependency "devise", "~> 3.1"
|
23
|
+
spec.add_dependency "omniauth", "~> 1.1"
|
24
|
+
spec.add_dependency "omniauth-oauth2", "~> 1.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "rspec"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wcc-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis Petticrew
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cancancan
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.9.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.9.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: devise
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: omniauth
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: omniauth-oauth2
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Authentication / Authorization library for Watermark apps
|
112
|
+
email:
|
113
|
+
- tpetticrew@watermark.org
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- lib/omniauth/strategies/watermark.rb
|
125
|
+
- lib/wcc/auth.rb
|
126
|
+
- lib/wcc/auth/ability.rb
|
127
|
+
- lib/wcc/auth/access_level.rb
|
128
|
+
- lib/wcc/auth/config.rb
|
129
|
+
- lib/wcc/auth/controller_helpers.rb
|
130
|
+
- lib/wcc/auth/devise.rb
|
131
|
+
- lib/wcc/auth/devise/watermark_callbacks_controller.rb
|
132
|
+
- lib/wcc/auth/providers.rb
|
133
|
+
- lib/wcc/auth/providers/active_record.rb
|
134
|
+
- lib/wcc/auth/version.rb
|
135
|
+
- spec/spec_helper.rb
|
136
|
+
- spec/wcc/auth/access_level_spec.rb
|
137
|
+
- spec/wcc/auth/config_spec.rb
|
138
|
+
- spec/wcc/auth/tiered_ability_spec.rb
|
139
|
+
- spec/wcc_auth_spec.rb
|
140
|
+
- wcc-auth.gemspec
|
141
|
+
homepage: https://github.com/watermarkchurch/wcc-auth
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.2.2
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: '# WCC::Auth Provides the necessary tools for handling authentication through
|
165
|
+
Watermark''s OAuth provider as well as authorizing the user has access to specific
|
166
|
+
features within the application. There are special hooks for Rails apps using Devise,
|
167
|
+
but the primitive structures could be used on any Ruby project. Currently, the only
|
168
|
+
tested path is Rails with Devise. ## Installation Add this line to your application''s
|
169
|
+
Gemfile: ```ruby gem ''wcc-auth'', ''~> 0.3.0'' ``` If you are using a Rails app
|
170
|
+
with Devise you can use a special require hook that will setup all the Devise specific
|
171
|
+
configuration for you. ```ruby gem ''wcc-auth'', ''~> 0.3.0'', require: ''wcc/auth/devise''
|
172
|
+
``` ## Configuration There are a few steps to setup your app. These instructions
|
173
|
+
are specific to a Rails app. #### Add the configuration block to an initializer In
|
174
|
+
order to configure the gem you must run the `WCC::Auth.setup` block. See below for
|
175
|
+
an example: ```ruby WCC::Auth.setup do |config| config.app_name = "app-name" config.environment
|
176
|
+
= Rails.env config.app_id = ''app-client-id-from-oauth-provider'' config.app_secret
|
177
|
+
= ''app-client-secret-from-oauth-provider'' end ``` #### Setup your controllers ```ruby
|
178
|
+
# Add this include to your ApplicationController class ApplicationController < ActionController::Base
|
179
|
+
include WCC::Auth::ControllerHelpers end ``` #### Setup your user model ```ruby
|
180
|
+
class User < ActiveRecord::Base include WCC::Auth::Providers::ActiveRecord # ... end
|
181
|
+
``` #### Setup authorization (optional) If you would like to use the `TieredAbility`
|
182
|
+
class included with `WCC::Auth` just create an Ability class that extends the `WCC::Auth::TieredAbility`
|
183
|
+
class. The authenticated user will include an info variables called `access_level_id`.
|
184
|
+
This corresponds to a `WCC::Auth::AccessLevel`. The access levels are broken down
|
185
|
+
into 5 tiers with the following rules: * **No access** -- This is the default level
|
186
|
+
* **Basic** -- This is provides read-only access * **Contribute** -- Read-write
|
187
|
+
for only data the user owns * **Manage** -- Read-write for other''s data * **App
|
188
|
+
Admin** -- Can change app configuration * **System Admin** -- Has full access to
|
189
|
+
all features always Each tier inherits all priveleges of the lower tiers. The rules
|
190
|
+
here are guidelines for the app to follow. It is ultimately up to the client application
|
191
|
+
to decide what each of these tiers means for it. Do your best to adhere to these
|
192
|
+
rules. Here is an example Ability class using the DSL provided by `WCC::Auth`. ```ruby
|
193
|
+
class Ability < WCC::Auth::TieredAbility at_level(:contribute) do |user| can :read,
|
194
|
+
Person can :manage, Task, created_by_id: user.id can :manage, Comment, created_by_id:
|
195
|
+
user.id cannot :destroy, Task end at_level(:appadmin) do |user| can :manage, :all
|
196
|
+
cannot :create, TaskGroup end at_level(:sysadmin) do |user| can :manage, :all
|
197
|
+
end end ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout
|
198
|
+
-b my-new-feature`) 3. Commit your changes (`git commit -am ''Add some feature''`)
|
199
|
+
4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request'
|
200
|
+
test_files:
|
201
|
+
- spec/spec_helper.rb
|
202
|
+
- spec/wcc/auth/access_level_spec.rb
|
203
|
+
- spec/wcc/auth/config_spec.rb
|
204
|
+
- spec/wcc/auth/tiered_ability_spec.rb
|
205
|
+
- spec/wcc_auth_spec.rb
|