wine_bouncer 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 33c8d82f673d5c4f4354784ebcab2081d5562b25
4
- data.tar.gz: 271ef498942ca797c4102849fdb9f9b2ed6885f4
3
+ metadata.gz: 7c429b9513442e2e74645fa55616dadaebcd810b
4
+ data.tar.gz: 50f3d5987a0ba20848dd943cfdeb9727b450ce4c
5
5
  SHA512:
6
- metadata.gz: 24fff99dcf71fcc75aea4765bb7f7f2b8c3b63d14cde019cca8161792dbfd0bbcafe1a4a7f92dda4e9d0475cbee74f82a27f4b983d8524aeb95b384f39f7f41d
7
- data.tar.gz: 2470e61c6cb6f4321808105b006a6138c88f3860637327588de6c6cff0082d547ff0d6d41acece1af02904766fb80951a7b5cb112747752de910fad8322122d5
6
+ metadata.gz: 0827d2f70a783e44db8fbd94c7a91ccca07b2c84b616dc669e072f438688b0501ec63bc25e5f3ba26911b7b2e1ddeaacbdb648cd6da5230552cefbc42321738a
7
+ data.tar.gz: 9f9cf89baafbb298e2f317f7801ea916c2e11722ece9b9975f2ddc447199d659934ce553b3bf30074a2c53d602a53d883bd6ea5e18f23c5639cd0d26029c5bfc
data/.travis.yml CHANGED
@@ -10,4 +10,7 @@ env:
10
10
  - rails=3.1.12
11
11
  - rails=3.2.18
12
12
  - rails=4.0.5
13
- - rails=4.1.1
13
+ - rails=4.1.1
14
+ addons:
15
+ code_climate:
16
+ repo_token: f010f83a5eb6671c85aaa0aec44c1fd43404537a4f4898d1b9ab248ac8a1487d
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ Changelog
2
+ =========
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,58 @@
1
+ Changelog
2
+ =========
3
+
4
+
5
+
6
+ ## Fork the project
7
+
8
+ Fork the project on the [project page]( https://github.com/Antek-drzewiecki/wine_bouncer/fork )
9
+
10
+ Checkout the project from your own repository.
11
+
12
+ ```
13
+ git clone https://github.com/[your github username]/wine_bouncer.git
14
+ cd wine_bouncer
15
+ git remote add upstream https://github.com/Antek-drzewiecki/wine_bouncer.git
16
+ ```
17
+
18
+ ## Create your feature branch
19
+
20
+ Make sure your fork is [up to date](https://help.github.com/articles/syncing-a-fork) and make a feature from the master branch.
21
+
22
+ `git checkout -b my-new-feature`
23
+
24
+ ## Prepare your development environment
25
+
26
+ See [README](README.md#development)
27
+
28
+ Run your specs to make sure nothing is broken ;)
29
+
30
+ ## Write Tests and/or Code
31
+
32
+ We appreciate pull requests. Even specs only to identify an problem!
33
+
34
+ Because we want to ensure the quality of our gem. We cannot accept functional changes without tests.
35
+
36
+ ## Write documentation
37
+
38
+ Documentation is appreciated. Its nice to know people know how to use features.
39
+ Also feel free to update the changelog with the changes.
40
+
41
+ ## Commit your changes
42
+
43
+ Commit your changes.
44
+ `git add ...`
45
+ `git commit -m 'Add some feature'`
46
+
47
+ ## Push your changes
48
+
49
+ Push to the branch.
50
+
51
+ `git push origin my-new-feature`
52
+
53
+ ## Create a pull request
54
+
55
+ Check your feature branch, once its passes Travis-CI, create a pull request
56
+
57
+
58
+ Thanks you for participating!
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ gem 'rails', ENV['rails']
5
5
 
6
6
  gem 'activerecord'
7
7
 
8
+ gem "codeclimate-test-reporter", group: :test, require: nil
8
9
 
9
10
  # Specify your gem's dependencies in wine_bouncer.gemspec
10
11
  gemspec
data/README.md CHANGED
@@ -1,9 +1,31 @@
1
1
  # WineBouncer
2
2
 
3
3
  [![Build Status](https://travis-ci.org/Antek-drzewiecki/wine_bouncer.svg?branch=master)](https://travis-ci.org/Antek-drzewiecki/wine_bouncer)
4
+ [![Code Climate](https://codeclimate.com/github/Antek-drzewiecki/wine_bouncer/badges/gpa.svg)](https://codeclimate.com/github/Antek-drzewiecki/wine_bouncer)
5
+ [![Gem Version](https://badge.fury.io/rb/wine_bouncer.svg)](http://badge.fury.io/rb/wine_bouncer)
4
6
 
7
+ Protect your precious Grape API with Doorkeeper.
8
+ WineBouncer uses minimal modification, to make the magic happen.
5
9
 
6
- TODO: Write a gem description
10
+ Table of Contents
11
+ =================
12
+ * [Requirements](#requirements)
13
+ * [Installation](#installation)
14
+ * [Upgrading](#upgrading)
15
+ * [Usage](#usage)
16
+ * [Authentication strategies](#authentication-strategies)
17
+ * [Default](#default)
18
+ * [Swagger](#swagger)
19
+ * [Token information](#token-information)
20
+ * [Exceptions and Exception handling](#exceptions-and-exception-handling)
21
+ * [Development](#development)
22
+ * [Contributing](#contributing)
23
+
24
+
25
+ ## Requirements
26
+ - Ruby >1.9.3
27
+ - Doorkeeper > 1.4.0
28
+ - Grape > 0.8.0
7
29
 
8
30
  ## Installation
9
31
 
@@ -15,20 +37,179 @@ gem 'wine_bouncer'
15
37
 
16
38
  And then execute:
17
39
 
18
- $ bundle
19
-
20
- Or install it yourself as:
40
+ ```ruby
41
+ bundle
42
+ ```
21
43
 
22
- $ gem install wine_bouncer
44
+ ## Upgrading
45
+ When upgrading from a previous version, see [UPGRADING](UPGRADING.md). You might also be interested at the [CHANGELOG](CHANGELOG.md).
23
46
 
24
47
  ## Usage
48
+ WineBouncer is a custom Grape Middleware used for Authentication and Authorization. We assume you have a Grape API mounted in your Rails application together with Doorkeeper.
49
+
50
+ To get started with WineBouncer, create a rails initializer in your Rails app at `config/initializer/wine_bouncer.rb` with the following configuration.
51
+
52
+ ``` ruby
53
+ WineBouncer.configure do |config|
54
+ config.auth_strategy = :default
55
+ end
56
+ ```
57
+
58
+ Then register WineBouncer as Grape middleware in your Grape API.
59
+
60
+ ``` ruby
61
+ class Api < Grape::API
62
+ default_format :json
63
+ format :json
64
+ use ::WineBouncer::OAuth2
65
+ mount YourAwesomeApi
66
+ end
67
+ ```
68
+
69
+ WineBouncer relies on Grape's endpoint method description to define if an endpoint method should be protected.
70
+ It comes with authorization strategies that allow a custom format for your authorization definition. Pick an authentication strategy to get started.
71
+ Currently the following strategies are included:
72
+
73
+ ### Authentication strategies
74
+
75
+ #### Default
76
+ The default strategy uses the `auth:` key in the description options hash to define API method authentication. It accepts an hash with options. Currently the only option is for scopes which is an array of scopes for authorization.
77
+ WineBouncer uses the default Doorkeeper behaviour for scopes.
78
+
79
+ Example:
80
+
81
+ ``` ruby
82
+ class MyAwesomeAPI < Grape::API
83
+ desc 'protected method with required public scope',
84
+ auth: { scopes: ['public'] }
85
+ get '/protected' do
86
+ { hello: 'world' }
87
+ end
88
+
89
+ desc 'Unprotected method'
90
+ get '/unprotected' do
91
+ { hello: 'unprotected world' }
92
+ end
93
+
94
+ desc 'This method needs the public or private scope.',
95
+ auth: { scopes: [ 'public', 'private' ] }
96
+ get '/method' do
97
+ { hello: 'public or private user.' }
98
+ end
99
+
100
+ desc 'This method uses Doorkeepers default scopes.',
101
+ auth: { scopes: [] }
102
+ get '/protected_with_default_scope' do
103
+ { hello: 'protected unscoped world' }
104
+ end
105
+ end
106
+
107
+ class Api < Grape::API
108
+ default_format :json
109
+ format :json
110
+ use ::WineBouncer::OAuth2
111
+ mount MyAwesomeAPI
112
+ add_swagger_documentation
113
+ end
114
+ ```
115
+
116
+
117
+ #### Swagger
118
+
119
+ WineBouncer comes with a strategy that can be perfectly used with [grape-swagger](https://github.com/tim-vandecasteele/grape-swagger) with a syntax compliant with the [swagger spec](https://github.com/wordnik/swagger-spec/).
120
+ This might be one of the simplest methods to protect your API and serve it with documentation. You can use [swagger-ui](https://github.com/wordnik/swagger-ui) to view your documentation.
121
+
122
+ To get started ensure you also have included the `grape-swagger` gem in your gemfile.
123
+
124
+ Run `bundle` to install the missing gems.
125
+
126
+ Create a rails initializer in your Rails app at `config/initializer/wine_bouncer.rb` with the following configuration.
127
+
128
+ ``` ruby
129
+ WineBouncer.configure do |config|
130
+ config.auth_strategy = :swagger
131
+ end
132
+ ```
133
+
134
+ Then you can start documenting and protecting your API like the example below.
135
+
136
+ ``` ruby
137
+
138
+ class MyAwesomeAPI < Grape::API
139
+ desc 'protected method with required public scope',
140
+ authorizations: { oauth2: [{ scope: 'public' }] }
141
+ get '/protected' do
142
+ { hello: 'world' }
143
+ end
144
+
145
+ desc 'Unprotected method'
146
+ get '/unprotected' do
147
+ { hello: 'unprotected world' }
148
+ end
149
+
150
+ desc 'This method uses Doorkeepers default scopes.',
151
+ authorizations: { oauth2: [] }
152
+ get '/protected_with_default_scope' do
153
+ { hello: 'protected unscoped world' }
154
+ end
155
+
156
+ desc 'It even works with other options!',
157
+ authorizations: { oauth2: [] },
158
+ :entity => Api::Entities::Response,
159
+ http_codes: [
160
+ [200, 'OK', Api::Entities::Response],
161
+ [401, 'Unauthorized', Api::Entities::Error],
162
+ [403, 'Forbidden', Api::Entities::Error]
163
+ ],
164
+ :notes => <<-NOTE
165
+
166
+ Marked down notes!
167
+
168
+ NOTE
169
+ get '/extended_api' do
170
+ { hello: 'Awesome world' }
171
+ end
172
+ end
173
+
174
+ class Api < Grape::API
175
+ default_format :json
176
+ format :json
177
+ use ::WineBouncer::OAuth2
178
+ mount MyAwesomeAPI
179
+ add_swagger_documentation
180
+ end
181
+ ```
182
+
183
+ The Swagger strategy uses the `authorizations: { oauth2: [] }` in the method description syntax to define API method authentication and authorization.
184
+ It defaults assumes when no description is given that no authorization should be used like the `/unprotected` method.
185
+ When the authentication syntax is mentioned in the method description, the method will be protected.
186
+ You can use the default scopes of Doorkeeper by just adding `authorizations: { oauth2: [] }` or state your own scopes with `authorizations: { oauth2: [ { scope: 'scope1' }, { scope: 'scope2' }, ... ] }`.
187
+
188
+ ### Token information
189
+
190
+ WineBouncer comes with free extras! Methods for `resource_owner` and `doorkeeper_access_token` get included in your endpoints. You can use them to get the current resource owner, and the access_token object of doorkeeper.
191
+
192
+ ## Exceptions and Exception handling
193
+
194
+ This gem raises the following exceptions which can be handled in your Grape API, see [Grape documentation](https://github.com/intridea/grape#exception-handling).
195
+
196
+ * `WineBouncer::Errors::OAuthUnauthorizedError`
197
+ when the request is unauthorized.
198
+ * `WineBouncer::Errors::OAuthForbiddenError`
199
+ when the token is found but scopes do not match.
200
+
201
+ ## Development
202
+
203
+ Since we want the gem tested against several rails versions we use the same way to prepare our development environment as Doorkeeper.
204
+
205
+ To install the development environment for rails 3.2.18, you can also specify a different rails version to test against.
206
+
207
+ `rails=3.2.18 bundle install`
208
+
209
+ To run the specs.
25
210
 
26
- TODO: Write usage instructions here
211
+ `rails=3.2.18 bundle exec rake`
27
212
 
28
213
  ## Contributing
29
214
 
30
- 1. Fork it ( https://github.com/[my-github-username]/wine_bouncer/fork )
31
- 2. Create your feature branch (`git checkout -b my-new-feature`)
32
- 3. Commit your changes (`git commit -am 'Add some feature'`)
33
- 4. Push to the branch (`git push origin my-new-feature`)
34
- 5. Create a new Pull Request
215
+ For contributing to the gem see [CONTRIBUTING](CONTRIBUTING.md).
data/UPGRADING.md ADDED
@@ -0,0 +1,4 @@
1
+ Upgrading WineBouncer
2
+ =====================
3
+
4
+ From version 0.1.0 upgrade instructions are given.
@@ -2,10 +2,22 @@ module WineBouncer
2
2
  module AuthMethods
3
3
  attr_accessor :doorkeeper_access_token
4
4
 
5
- def current_user
5
+ def protected_endpoint=(protected)
6
+ @protected_endpoint= protected
7
+ end
8
+
9
+ def protected_endpoint?
10
+ @protected_endpoint || false
11
+ end
12
+
13
+ def resource_owner
6
14
  User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
7
15
  end
8
16
 
17
+ def client_credential_token?
18
+ has_doorkeeper_token? && doorkeeper_access_token.resource_owner_id.nil?
19
+ end
20
+
9
21
  def doorkeeper_access_token
10
22
  @_doorkeeper_access_token
11
23
  end
@@ -0,0 +1,28 @@
1
+ module WineBouncer
2
+ module AuthStrategies
3
+ class Default
4
+
5
+ def endpoint_protected?(context)
6
+ has_authorizations?(context)
7
+ end
8
+
9
+ def has_auth_scopes?(context)
10
+ has_authorizations?(context) && endpoint_authorizations(context).has_key?(:scopes) && !endpoint_authorizations(context)[:scopes].empty?
11
+ end
12
+
13
+ def auth_scopes(context)
14
+ endpoint_authorizations(context)[:scopes].map(&:to_sym)
15
+ end
16
+
17
+ private
18
+
19
+ def has_authorizations?(context)
20
+ !!endpoint_authorizations(context)
21
+ end
22
+
23
+ def endpoint_authorizations(context)
24
+ context[:auth]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module WineBouncer
2
+ module AuthStrategies
3
+ class Swagger
4
+
5
+ def endpoint_protected?(context)
6
+ has_authorizations?(context) && !!authorization_type_oauth2(context)
7
+ end
8
+
9
+ def has_auth_scopes?(context)
10
+ endpoint_protected?(context) && !authorization_type_oauth2(context).empty?
11
+ end
12
+
13
+ def auth_scopes(context)
14
+ authorization_type_oauth2(context).map{ |hash| hash[:scope].to_sym }
15
+ end
16
+
17
+ private
18
+
19
+ def has_authorizations?(context)
20
+ !!endpoint_authorizations(context)
21
+ end
22
+
23
+ def endpoint_authorizations(context)
24
+ context[:authorizations]
25
+ end
26
+
27
+ def authorization_type_oauth2(context)
28
+ endpoint_authorizations(context)[:oauth2]
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module WineBouncer
2
+
3
+ class << self
4
+ attr_accessor :configuration
5
+ end
6
+
7
+ class Configuration
8
+
9
+ attr_accessor :auth_strategy
10
+
11
+ def auth_strategy=(strategy)
12
+ @auth_strategy= strategy
13
+ end
14
+
15
+ def auth_strategy
16
+ @auth_strategy || :default
17
+ end
18
+
19
+ def require_strategies
20
+ require "wine_bouncer/auth_strategies/#{auth_strategy}"
21
+ end
22
+ end
23
+
24
+ def self.configuration
25
+ @configuration || fail(Errors::UnconfiguredError.new)
26
+ end
27
+
28
+ def self.configuration=(config)
29
+ @configuration= config
30
+ @configuration.require_strategies
31
+ end
32
+
33
+ ###
34
+ # Configure block.
35
+ # Requires all strategy specific files.
36
+ ###
37
+ def self.configure
38
+ yield(config)
39
+ config.require_strategies
40
+ config
41
+ end
42
+
43
+ private
44
+
45
+ ###
46
+ # Returns a new configuration or existing one.
47
+ ###
48
+ def self.config
49
+ @configuration ||= Configuration.new
50
+ end
51
+ end
@@ -1,5 +1,6 @@
1
1
  module WineBouncer
2
2
  module Errors
3
+ class UnconfiguredError < StandardError; end
3
4
  class OAuthUnauthorizedError < StandardError; end
4
5
  class OAuthForbiddenError < StandardError; end
5
6
  end
@@ -8,9 +8,9 @@ module WineBouncer
8
8
  env['api.endpoint']
9
9
  end
10
10
 
11
- ###
11
+ ############
12
12
  # DoorKeeper stuff.
13
- ###
13
+ ############
14
14
 
15
15
  ###
16
16
  # Sets and converts a rack request to a ActionDispatch request, which is required for DoorKeeper to function.
@@ -19,7 +19,6 @@ module WineBouncer
19
19
  @_doorkeeper_request = ActionDispatch::Request.new(env)
20
20
  end
21
21
 
22
-
23
22
  ###
24
23
  # Returns the request context.
25
24
  ###
@@ -41,31 +40,26 @@ module WineBouncer
41
40
  doorkeeper_token && doorkeeper_token.acceptable?(scopes)
42
41
  end
43
42
 
44
- ###
43
+ ############
45
44
  # Authorization control.
46
- ###
45
+ ############
47
46
 
48
47
  ###
49
48
  # Returns true if the Api endpoint, method is configured as an protected method, false otherwise.
50
49
  ###
51
- def has_authorizations?
52
- context && context.options && context.options[:route_options] && endpoint_authorizations
50
+ def valid_route_context?
51
+ context && context.options && context.options[:route_options]
53
52
  end
54
53
 
55
- ###
56
- # Returns the endpoint authorizations hash.
57
- # This hash contains all authorization methods.
58
- ###
59
- def endpoint_authorizations
60
- @_authorizations ||= context.options[:route_options][:authorizations]
54
+ def route_context
55
+ context.options[:route_options]
61
56
  end
62
57
 
63
58
  ###
64
59
  # returns true if the endpoint is protected, otherwise false
65
- # Currently it only accepts oauth2.
66
60
  ###
67
61
  def endpoint_protected?
68
- has_authorizations? && !!endpoint_authorizations[:oauth2]
62
+ auth_strategy.endpoint_protected?(route_context)
69
63
  end
70
64
 
71
65
  ###
@@ -73,8 +67,8 @@ module WineBouncer
73
67
  # [ nil ] if none, otherwise an array of [ :scopes ]
74
68
  ###
75
69
  def auth_scopes
76
- return *nil if endpoint_authorizations[:oauth2].empty?
77
- endpoint_authorizations[:oauth2].map{|hash| hash[:scope].to_sym}
70
+ return *nil unless auth_strategy.has_auth_scopes?(route_context)
71
+ auth_strategy.auth_scopes(route_context)
78
72
  end
79
73
 
80
74
  ###
@@ -96,20 +90,36 @@ module WineBouncer
96
90
  end
97
91
  end
98
92
 
99
- ###
93
+ ############
100
94
  # Grape middleware methods
101
- ###
95
+ ############
102
96
 
103
97
  ###
104
98
  # Before do.
105
99
  ###
106
100
  def before
107
- return unless endpoint_protected?
101
+ set_auth_strategy(WineBouncer.configuration.auth_strategy)
102
+ #extend the context with auth methods.
103
+ context.extend(WineBouncer::AuthMethods)
104
+ context.protected_endpoint = endpoint_protected?
105
+ return unless context.protected_endpoint?
108
106
  self.doorkeeper_request= env # set request for later use.
109
107
  doorkeeper_authorize! *auth_scopes
110
- env['api.endpoint'].extend(WineBouncer::AuthMethods)
111
- env['api.endpoint'].doorkeeper_access_token = doorkeeper_token
108
+ context.doorkeeper_access_token = doorkeeper_token
109
+ end
110
+
111
+ ###
112
+ # Strategy
113
+ ###
114
+ def auth_strategy
115
+ @auth_strategy
116
+ end
117
+
118
+ private
119
+
120
+ def set_auth_strategy(strategy)
121
+ @auth_strategy = WineBouncer::AuthStrategies.const_get("#{strategy.to_s.capitalize}").new
112
122
  end
113
123
 
114
124
  end
115
- end
125
+ end
@@ -1,3 +1,3 @@
1
1
  module WineBouncer
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/wine_bouncer.rb CHANGED
@@ -2,6 +2,7 @@ require 'grape'
2
2
  require 'doorkeeper'
3
3
  require 'wine_bouncer/version'
4
4
  require 'wine_bouncer/errors'
5
+ require 'wine_bouncer/configuration'
5
6
  require 'wine_bouncer/oauth2'
6
7
  require 'wine_bouncer/auth_methods/auth_methods'
7
8
 
@@ -3,33 +3,38 @@ module Api
3
3
  ###
4
4
  # Api under test, default doorkeeper scope is 'account'
5
5
  ##
6
- class MountedApiUnderTest < Grape::API
7
- desc 'Document root', authorizations: { oauth2: [{ scope: 'public', description: 'anything' }] }
6
+ class MountedDefaultApiUnderTest < Grape::API
7
+ desc 'Protected method with public', auth: { scopes: ['public'] }
8
8
  get '/protected' do
9
9
  { hello: 'world' }
10
10
  end
11
- desc 'Document root', authorizations: { oauth2: [{ scope: 'private', description: 'anything' }] }
11
+
12
+ desc 'Protected method with private', auth: { scopes: ['private'] }
12
13
  get '/protected_with_private_scope' do
13
14
  { hello: 'scoped world' }
14
15
  end
16
+
17
+ desc 'Unprotected method'
15
18
  get '/unprotected' do
16
19
  { hello: 'unprotected world' }
17
20
  end
18
- desc 'Document root', authorizations: { oauth2: [{ scope: 'public', description: 'anything' }] }
21
+
22
+ desc 'Protected method with public that returns the user name', auth: { scopes: ['public'] }
19
23
  get '/protected_user' do
20
- { hello: current_user.name }
24
+ { hello: resource_owner.name }
21
25
  end
22
- desc 'Document root', authorizations: { oauth2: [] }
26
+
27
+ desc 'This method uses Doorkeepers default scopes', auth: { }
23
28
  get '/protected_without_scope' do
24
29
  { hello: 'protected unscoped world' }
25
30
  end
26
31
  end
27
32
 
28
- class ApiUnderTest < Grape::API
33
+ class DefaultApiUnderTest < Grape::API
29
34
  default_format :json
30
35
  format :json
31
36
  use ::WineBouncer::OAuth2
32
- mount MountedApiUnderTest
37
+ mount MountedDefaultApiUnderTest
33
38
  end
34
39
 
35
40
  end
@@ -0,0 +1,39 @@
1
+ module Api
2
+
3
+ ###
4
+ # Api under test, default doorkeeper scope is 'account'
5
+ ##
6
+ class MountedSwaggerApiUnderTest < Grape::API
7
+ desc 'Protected method with public', authorizations: { oauth2: [{ scope: 'public', description: 'anything' }] }
8
+ get '/protected' do
9
+ { hello: 'world' }
10
+ end
11
+
12
+ desc 'Protected method with private', authorizations: { oauth2: [{ scope: 'private', description: 'anything' }] }
13
+ get '/protected_with_private_scope' do
14
+ { hello: 'scoped world' }
15
+ end
16
+
17
+ desc 'Unprotected method'
18
+ get '/unprotected' do
19
+ { hello: 'unprotected world' }
20
+ end
21
+
22
+ desc 'Protected method with public that returns the user name', authorizations: { oauth2: [{ scope: 'public', description: 'anything' }] }
23
+ get '/protected_user' do
24
+ { hello: resource_owner.name }
25
+ end
26
+
27
+ desc 'This method uses Doorkeepers default scopes', authorizations: { oauth2: [] }
28
+ get '/protected_without_scope' do
29
+ { hello: 'protected unscoped world' }
30
+ end
31
+ end
32
+
33
+ class SwaggerApiUnderTest < Grape::API
34
+ default_format :json
35
+ format :json
36
+ use ::WineBouncer::OAuth2
37
+ mount MountedSwaggerApiUnderTest
38
+ end
39
+ end
@@ -7,11 +7,11 @@ require "action_mailer/railtie"
7
7
  require "action_view/railtie"
8
8
  require "sprockets/railtie"
9
9
 
10
+ require "wine_bouncer"
10
11
  # require "rails/test_unit/railtie"
11
12
 
12
13
  Bundler.require(*Rails.groups)
13
- require 'doorkeeper'
14
- require "wine_bouncer"
14
+
15
15
 
16
16
  module Dummy
17
17
  class Application < Rails::Application
@@ -1,4 +1,4 @@
1
- Rails.application.configure do
1
+ Dummy::Application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb.
3
3
 
4
4
  # The test environment is used exclusively to run your application's
@@ -0,0 +1 @@
1
+ Dummy::Application.config.secret_token = 'c65fd1ffec8275651d1fd06ec3a4914ba644bbeb87729594a3b35fb4b7ad4cccd298d77baf63f7a6513d437e5b95eef9637f9c37a9691c3ed41143d8b5f9a5ef'
@@ -1,4 +1,5 @@
1
1
  Rails.application.routes.draw do
2
2
  use_doorkeeper
3
- mount Api::ApiUnderTest => '/api'
3
+ mount Api::DefaultApiUnderTest => '/default_api'
4
+ mount Api::SwaggerApiUnderTest => '/swagger_api'
4
5
  end
@@ -1,16 +1,21 @@
1
1
  require 'rails_helper'
2
2
  require 'json'
3
3
 
4
- describe Api::MountedApiUnderTest, type: :api do
4
+ describe Api::MountedDefaultApiUnderTest, type: :api do
5
5
 
6
6
  let(:user) { FactoryGirl.create :user }
7
7
  let(:token) { FactoryGirl.create :clientless_access_token, resource_owner_id: user.id, scopes: "public" }
8
8
  let(:unscoped_token) { FactoryGirl.create :clientless_access_token, resource_owner_id: user.id, scopes: "" }
9
9
 
10
+ before (:example) do
11
+ WineBouncer.configure do |c|
12
+ c.auth_strategy = :default
13
+ end
14
+ end
15
+
10
16
  context 'tokens and scopes' do
11
17
  it 'gives access when the token and scope are correct' do
12
-
13
- get '/api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
18
+ get '/default_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
14
19
 
15
20
  expect(last_response.status).to eq(200)
16
21
  json = JSON.parse(last_response.body)
@@ -18,21 +23,21 @@ describe Api::MountedApiUnderTest, type: :api do
18
23
  end
19
24
 
20
25
  it 'raises an authentication error when the token is invalid' do
21
- expect { get '/api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
26
+ expect { get '/default_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
22
27
  end
23
28
 
24
29
  it 'raises an oauth authentication error when no token is given' do
25
- expect { get '/api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
30
+ expect { get '/default_api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
26
31
  end
27
32
 
28
33
  it 'raises an auth forbidden authentication error when the user scope is not correct' do
29
- expect { get '/api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
34
+ expect { get '/default_api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
30
35
  end
31
36
  end
32
37
 
33
38
  context 'unprotected endpoint' do
34
39
  it 'allows to call an unprotected endpoint without token' do
35
- get '/api/unprotected'
40
+ get '/default_api/unprotected'
36
41
 
37
42
  expect(last_response.status).to eq(200)
38
43
  json = JSON.parse(last_response.body)
@@ -42,7 +47,7 @@ describe Api::MountedApiUnderTest, type: :api do
42
47
  end
43
48
 
44
49
  it 'allows to call an unprotected endpoint with token' do
45
- get '/api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
50
+ get '/default_api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
46
51
 
47
52
  expect(last_response.status).to eq(200)
48
53
  json = JSON.parse(last_response.body)
@@ -54,7 +59,7 @@ describe Api::MountedApiUnderTest, type: :api do
54
59
  context 'protected_without_scopes' do
55
60
 
56
61
  it 'allows to call an protected endpoint without scopes' do
57
- get '/api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
62
+ get '/default_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
58
63
 
59
64
  expect(last_response.status).to eq(200)
60
65
  json = JSON.parse(last_response.body)
@@ -63,17 +68,17 @@ describe Api::MountedApiUnderTest, type: :api do
63
68
  end
64
69
 
65
70
  it 'raises an error when an protected endpoint without scopes is called without token ' do
66
- expect { get '/api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
71
+ expect { get '/default_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
67
72
  end
68
73
 
69
74
  it 'raises an error because the user does not have the default scope' do
70
- expect { get '/api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
75
+ expect { get '/default_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
71
76
  end
72
77
  end
73
78
 
74
- context 'current_user' do
79
+ context 'resource_owner' do
75
80
  it 'is available in the endpoint' do
76
- get '/api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
81
+ get '/default_api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
77
82
 
78
83
  expect(last_response.status).to eq(200)
79
84
  json = JSON.parse(last_response.body)
@@ -0,0 +1,93 @@
1
+ require 'rails_helper'
2
+ require 'json'
3
+
4
+ describe Api::MountedSwaggerApiUnderTest, type: :api do
5
+
6
+ let(:user) { FactoryGirl.create :user }
7
+ let(:token) { FactoryGirl.create :clientless_access_token, resource_owner_id: user.id, scopes: "public" }
8
+ let(:unscoped_token) { FactoryGirl.create :clientless_access_token, resource_owner_id: user.id, scopes: "" }
9
+
10
+
11
+ before (:example) do
12
+ WineBouncer.configure do |c|
13
+ c.auth_strategy = :swagger
14
+ end
15
+ end
16
+
17
+ context 'tokens and scopes' do
18
+ it 'gives access when the token and scope are correct' do
19
+
20
+ get '/swagger_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
21
+
22
+ expect(last_response.status).to eq(200)
23
+ json = JSON.parse(last_response.body)
24
+ expect(json).to have_key('hello')
25
+ end
26
+
27
+ it 'raises an authentication error when the token is invalid' do
28
+ expect { get '/swagger_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
29
+ end
30
+
31
+ it 'raises an oauth authentication error when no token is given' do
32
+ expect { get '/swagger_api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
33
+ end
34
+
35
+ it 'raises an auth forbidden authentication error when the user scope is not correct' do
36
+ expect { get '/swagger_api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
37
+ end
38
+ end
39
+
40
+ context 'unprotected endpoint' do
41
+ it 'allows to call an unprotected endpoint without token' do
42
+ get '/swagger_api/unprotected'
43
+
44
+ expect(last_response.status).to eq(200)
45
+ json = JSON.parse(last_response.body)
46
+
47
+ expect(json).to have_key('hello')
48
+ expect(json['hello']).to eq('unprotected world')
49
+ end
50
+
51
+ it 'allows to call an unprotected endpoint with token' do
52
+ get '/swagger_api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
53
+
54
+ expect(last_response.status).to eq(200)
55
+ json = JSON.parse(last_response.body)
56
+ expect(json).to have_key('hello')
57
+ expect(json['hello']).to eq('unprotected world')
58
+ end
59
+ end
60
+
61
+ context 'protected_without_scopes' do
62
+
63
+ it 'allows to call an protected endpoint without scopes' do
64
+ get '/swagger_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
65
+
66
+ expect(last_response.status).to eq(200)
67
+ json = JSON.parse(last_response.body)
68
+ expect(json).to have_key('hello')
69
+ expect(json['hello']).to eq('protected unscoped world')
70
+ end
71
+
72
+ it 'raises an error when an protected endpoint without scopes is called without token ' do
73
+ expect { get '/swagger_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
74
+ end
75
+
76
+ it 'raises an error because the user does not have the default scope' do
77
+ expect { get '/swagger_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
78
+ end
79
+ end
80
+
81
+ context 'resource_owner' do
82
+ it 'is available in the endpoint' do
83
+ get '/swagger_api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
84
+
85
+ expect(last_response.status).to eq(200)
86
+ json = JSON.parse(last_response.body)
87
+
88
+ expect(json).to have_key('hello')
89
+ expect(json['hello']).to eq(user.name)
90
+ end
91
+ end
92
+
93
+ end
@@ -48,4 +48,40 @@ describe ::WineBouncer::AuthMethods do
48
48
  expect(tested_class.has_doorkeeper_token?).to be false
49
49
  end
50
50
  end
51
+
52
+ context 'client_credential_token?' do
53
+ it 'return true if the doorkeeper token is aquired through client_credential authentication' do
54
+ token.resource_owner_id = nil
55
+ tested_class.doorkeeper_access_token = token
56
+ expect(tested_class.client_credential_token?).to be true
57
+ end
58
+
59
+ it 'return false if no token is set' do
60
+ token.resource_owner_id = nil
61
+ tested_class.doorkeeper_access_token
62
+ expect(tested_class.client_credential_token?).to be false
63
+ end
64
+
65
+ it 'return false if the token has a resource_owner' do
66
+ token.resource_owner_id = 2
67
+ tested_class.doorkeeper_access_token= token
68
+ expect(tested_class.client_credential_token?).to be false
69
+ end
70
+ end
71
+
72
+ context 'protected_endpoint?' do
73
+ it 'when set true it returns true' do
74
+ tested_class.protected_endpoint= true
75
+ expect(tested_class.protected_endpoint?).to be true
76
+ end
77
+
78
+ it 'when set false it returns false' do
79
+ tested_class.protected_endpoint= false
80
+ expect(tested_class.protected_endpoint?).to be false
81
+ end
82
+
83
+ it 'defaults returns false if not set' do
84
+ expect(tested_class.protected_endpoint?).to be false
85
+ end
86
+ end
51
87
  end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'wine_bouncer/auth_strategies/default'
3
+
4
+ describe ::WineBouncer::AuthStrategies::Default do
5
+ subject(:klass) { ::WineBouncer::AuthStrategies::Default.new }
6
+
7
+ let(:scopes) { [ 'public', 'private' ] }
8
+ let(:scopes_hash) { { scopes: scopes } }
9
+ let(:auth_context) { { auth: scopes_hash } }
10
+
11
+
12
+ context 'endpoint_authorizations' do
13
+ it 'returns the auth key of the authentication hash.' do
14
+ expect(klass.send(:endpoint_authorizations, auth_context)).to eq(scopes_hash)
15
+ end
16
+
17
+ it 'returns nil when the authentication key has no hash key.' do
18
+ invalid_hash = { some: scopes_hash }
19
+ expect(klass.send(:endpoint_authorizations, invalid_hash ) ).to eq(nil)
20
+ end
21
+ end
22
+
23
+ context 'has_authorizations?' do
24
+ it 'returns true when the context has the auth key.' do
25
+ expect(klass.send(:has_authorizations?, auth_context)).to eq(true)
26
+ end
27
+
28
+ it 'returns false when the context has no auth key.' do
29
+ invalid_hash = { some: scopes_hash }
30
+ expect(klass.send(:has_authorizations?, invalid_hash ) ).to eq(false)
31
+ end
32
+ end
33
+
34
+ context 'endpoint_protected?' do
35
+ it 'returns true when the context has the auth key.' do
36
+ expect(klass.endpoint_protected?(auth_context)).to eq(true)
37
+ end
38
+
39
+ it 'returns false when the context has no auth key.' do
40
+ invalid_hash = { some: scopes }
41
+ expect(klass.endpoint_protected?( invalid_hash ) ).to eq(false)
42
+ end
43
+ end
44
+
45
+ context 'has_auth_scopes?' do
46
+ it 'returns true when the context has the auth key.' do
47
+ expect(klass.has_auth_scopes?(auth_context)).to eq(true)
48
+ end
49
+
50
+ it 'returns false when the context has no auth key.' do
51
+ invalid_hash = { some: scopes_hash }
52
+ expect(klass.has_auth_scopes?(invalid_hash) ).to eq(false)
53
+ end
54
+
55
+ it 'returns false when the auth scopes contain a blank scope array' do
56
+ blank_scopes = { auth: { scopes: [] } }
57
+ expect(klass.has_auth_scopes?(blank_scopes) ).to eq(false)
58
+ end
59
+ end
60
+
61
+ context 'auth_scopes' do
62
+ it 'returns an array of scopes' do
63
+ expect(klass.auth_scopes(auth_context)).to eq([:public, :private])
64
+ end
65
+ end
66
+ end
data/spec/rails_helper.rb CHANGED
@@ -7,6 +7,10 @@ require 'rspec/rails'
7
7
  require 'wine_bouncer'
8
8
  require 'factory_girl'
9
9
  require 'database_cleaner'
10
+ require "codeclimate-test-reporter"
11
+
12
+ CodeClimate::TestReporter.start
13
+
10
14
  # Add additional requires below this line. Rails is not loaded until this point!
11
15
 
12
16
  # Requires supporting ruby files with custom matchers and macros, etc, in
@@ -54,7 +58,7 @@ RSpec.configure do |config|
54
58
  config.include FactoryGirl::Syntax::Methods
55
59
  config.include ApiHelper, :type=>:api
56
60
 
57
- config.use_transactional_fixtures = true
61
+ config.use_transactional_fixtures = false
58
62
 
59
63
 
60
64
  config.infer_spec_type_from_file_location!
data/wine_bouncer.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_runtime_dependency 'grape', '~> 0.9.0'
20
+ spec.add_runtime_dependency 'grape', '~> 0.8.0'
21
21
  spec.add_runtime_dependency 'doorkeeper', '~> 1.4.0'
22
22
 
23
23
  spec.add_development_dependency "railties"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wine_bouncer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antek Drzewiecki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-16 00:00:00.000000000 Z
11
+ date: 2014-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grape
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.0
19
+ version: 0.8.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.0
26
+ version: 0.8.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: doorkeeper
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -146,18 +146,25 @@ files:
146
146
  - .gitignore
147
147
  - .rspec
148
148
  - .travis.yml
149
+ - CHANGELOG.md
150
+ - CONTRIBUTING.md
149
151
  - Gemfile
150
152
  - LICENSE.txt
151
153
  - README.md
152
154
  - Rakefile
155
+ - UPGRADING.md
153
156
  - lib/wine_bouncer.rb
154
157
  - lib/wine_bouncer/auth_methods/auth_methods.rb
158
+ - lib/wine_bouncer/auth_strategies/default.rb
159
+ - lib/wine_bouncer/auth_strategies/swagger.rb
160
+ - lib/wine_bouncer/configuration.rb
155
161
  - lib/wine_bouncer/errors.rb
156
162
  - lib/wine_bouncer/oauth2.rb
157
163
  - lib/wine_bouncer/version.rb
158
164
  - spec/dummy/README.rdoc
159
165
  - spec/dummy/Rakefile
160
- - spec/dummy/app/api/api.rb
166
+ - spec/dummy/app/api/default_api.rb
167
+ - spec/dummy/app/api/swagger_api.rb
161
168
  - spec/dummy/app/assets/images/.keep
162
169
  - spec/dummy/app/assets/javascripts/application.js
163
170
  - spec/dummy/app/assets/stylesheets/application.css
@@ -187,6 +194,7 @@ files:
187
194
  - spec/dummy/config/initializers/filter_parameter_logging.rb
188
195
  - spec/dummy/config/initializers/inflections.rb
189
196
  - spec/dummy/config/initializers/mime_types.rb
197
+ - spec/dummy/config/initializers/secret_token.rb
190
198
  - spec/dummy/config/initializers/session_store.rb
191
199
  - spec/dummy/config/initializers/wrap_parameters.rb
192
200
  - spec/dummy/config/locales/doorkeeper.en.yml
@@ -205,8 +213,10 @@ files:
205
213
  - spec/factories/access_token.rb
206
214
  - spec/factories/application.rb
207
215
  - spec/factories/user.rb
208
- - spec/intergration/oauth2_spec.rb
216
+ - spec/intergration/oauth2_default_strategy_spec.rb
217
+ - spec/intergration/oauth2_swagger_strategy_spec.rb
209
218
  - spec/lib/wine_bouncer/auth_methods/auth_methods_spec.rb
219
+ - spec/lib/wine_bouncer/auth_strategies/default_spec.rb
210
220
  - spec/rails_helper.rb
211
221
  - spec/shared/orm/active_record.rb
212
222
  - spec/spec_helper.rb
@@ -238,7 +248,8 @@ summary: A Ruby gem that allows Oauth2 protection with Doorkeeper for Grape Api'
238
248
  test_files:
239
249
  - spec/dummy/README.rdoc
240
250
  - spec/dummy/Rakefile
241
- - spec/dummy/app/api/api.rb
251
+ - spec/dummy/app/api/default_api.rb
252
+ - spec/dummy/app/api/swagger_api.rb
242
253
  - spec/dummy/app/assets/images/.keep
243
254
  - spec/dummy/app/assets/javascripts/application.js
244
255
  - spec/dummy/app/assets/stylesheets/application.css
@@ -268,6 +279,7 @@ test_files:
268
279
  - spec/dummy/config/initializers/filter_parameter_logging.rb
269
280
  - spec/dummy/config/initializers/inflections.rb
270
281
  - spec/dummy/config/initializers/mime_types.rb
282
+ - spec/dummy/config/initializers/secret_token.rb
271
283
  - spec/dummy/config/initializers/session_store.rb
272
284
  - spec/dummy/config/initializers/wrap_parameters.rb
273
285
  - spec/dummy/config/locales/doorkeeper.en.yml
@@ -286,8 +298,10 @@ test_files:
286
298
  - spec/factories/access_token.rb
287
299
  - spec/factories/application.rb
288
300
  - spec/factories/user.rb
289
- - spec/intergration/oauth2_spec.rb
301
+ - spec/intergration/oauth2_default_strategy_spec.rb
302
+ - spec/intergration/oauth2_swagger_strategy_spec.rb
290
303
  - spec/lib/wine_bouncer/auth_methods/auth_methods_spec.rb
304
+ - spec/lib/wine_bouncer/auth_strategies/default_spec.rb
291
305
  - spec/rails_helper.rb
292
306
  - spec/shared/orm/active_record.rb
293
307
  - spec/spec_helper.rb