wcc-auth 0.3.0
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 +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
|