jko_api 0.1.3 → 0.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3711a73e0bd2f3bb5186bf24f11d4530b9bbeb6c
4
- data.tar.gz: e3a28ae1fc62c36cf5a3b77ee0f6820873fb9a75
3
+ metadata.gz: 2b71ffbde46caf94241d8813b48dc53c2b737f77
4
+ data.tar.gz: 06f6e116abebe5c5efcae4614a8e8c49178e69b5
5
5
  SHA512:
6
- metadata.gz: c6adaaad9e11e56f1971934b7522dae6cc7376e18ddae3b3358202ee73dc85452327d5eab3a62c594e05f525ced2320ff1df5b1acbcf03b8e8ce0faedcbadc6a
7
- data.tar.gz: 26a75d36b762ffa83d8f74f93374b5a843f73fdc9d1c80e3f969afdd68722d2055e9d527e239b722f70477304680f2de002490f19c041bace08d668cd4816bc3
6
+ metadata.gz: 3b0e444f0a11c75fedbd4e3d9865582b60179ad78f75f2213248619b2c3b17d9ac42a63e77429a6d79d3f2dba23f28372b86a9bfc1c91f59102f6b392ccc0061
7
+ data.tar.gz: 75ced80d1207ab1c3457d602a515e8d169b4a30572ee1cad16636ec888c479e24a6bef1f483cd9a4d811043fc666cfe4b127c0390ec512a0a7a7a6e8f18a6ef4
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'warden-oauth2-strategies', github: 'airservice/warden-oauth2', require: false
3
4
  # Specify your gem's dependencies in jko_api.gemspec
4
5
  gemspec
data/README.md CHANGED
@@ -6,7 +6,11 @@ This gem lets you make a single change and reversion an API without copying over
6
6
 
7
7
  ## Props
8
8
 
9
- 99% of this gem was written by [Justin Ko](https://github.com/justinko). Since he's lazy and won't make a gem from it, I'm doing it for him :stuck_out_tongue: That's why the gem is named after him.
9
+ 90% of the code was originally written by [Justin Ko](https://github.com/justinko). Since he's lazy and won't make a gem from it, I'm doing it for him :stuck_out_tongue: That's why the gem is named after him. I've since modified some of the stuff, and will continue to make it easier to use and more configurable.
10
+
11
+ ## Requirements
12
+
13
+ Rails 5 and Ruby 2.3
10
14
 
11
15
  ## Installation
12
16
 
@@ -31,9 +35,19 @@ In your `config/initializers/jko_api.rb` add
31
35
  ```ruby
32
36
  JkoApi.configure do |c|
33
37
  # This is the default. You can override this if you need a different controller
34
- # c.base_controller = Api::ApplicationController
38
+ # c.base_controller = ApiApplicationController
39
+
35
40
  # This is the folder name where all the api controllers would go
36
41
  # c.api_namespace = 'api'
42
+
43
+ # If you want to use your own authentication stuff, leave this as default
44
+ # c.use_authentication = false
45
+
46
+ # The built in authentication is Warden::OAuth2 with a Bearer strategy. Currently no other option exists
47
+ # c.strategy = :bearer
48
+
49
+ # If you use the authentication, then you will need to make some model, and set this value. More notes below.
50
+ # c.token_model = SomeModelYouCreated
37
51
  end
38
52
  ```
39
53
 
@@ -53,10 +67,10 @@ JkoApi.routes self do
53
67
  end
54
68
  ```
55
69
 
56
- Place your version 1 controller code in `app/controllers/api/v1`. Controllers should all inherit from `Api::ApplicationController`, or something that inherits from that.
70
+ Place your version 1 controller code in `app/controllers/api/v1`. Controllers should all inherit from `ApiApplicationController`, or something that inherits from that.
57
71
 
58
72
  ```ruby
59
- class Api::V1::BarsController < Api::ApplicationController
73
+ class Api::V1::BarsController < ApiApplicationController
60
74
  def index
61
75
  # do stuff
62
76
  end
@@ -80,7 +94,7 @@ end
80
94
  We can still do this though
81
95
 
82
96
  ```ruby
83
- class Api::V3::BarsController < Api::ApplicationController
97
+ class Api::V3::BarsController < ApiApplicationController
84
98
  def index
85
99
  # do different stuff
86
100
  end
@@ -89,6 +103,63 @@ end
89
103
 
90
104
  You can test this all by booting up a simple rails app, then do `curl -H "Accept: application/vnd.api.v1+json" http://localhost:3000/bars`
91
105
 
106
+ The `Accept` header is required to make the calls.
107
+
108
+ ## Authentication
109
+ If your API calls need to be authenticated by some API key, then you will need to configure a few more things.
110
+
111
+ ```ruby
112
+ JkoApi.configure do |c|
113
+ c.use_authentication = true
114
+ c.token_model = MyCustomTokenVerifier
115
+ end
116
+ ```
117
+
118
+ Your `MyCustomTokenVerifier` must be a ruby object that responds to a `locate` class method, and take a single string argument for the api token that needs to be authenticated. The method should return the token string if it's valid, and nil if it's not. You can [read more here](https://github.com/opperator/warden-oauth2#access-token).
119
+
120
+ This `MyCustomTokenVerifier` could look a few different ways. One way would be to make this a rails model like
121
+
122
+ ```ruby
123
+ class MyCustomTokenVerifier < ApplicationModel
124
+
125
+ def self.locate(token_string)
126
+ find_by(api_token: token_string)
127
+ end
128
+
129
+ end
130
+ ```
131
+
132
+ Another way would be a simple class that verifies against some configuration option like
133
+
134
+ ```ruby
135
+ class MyCustomTokenVerifier
136
+
137
+ def self.locate(token_string)
138
+ if token_string == Rails.configuration.x.my_custom_api_token
139
+ token_string
140
+ else
141
+ nil
142
+ end
143
+ end
144
+ end
145
+ ```
146
+
147
+ ## Testing
148
+ If you're trying to test controllers, keep in mind that Rails doesn't process the middleware stack for controller tests. This means that the `JkoApi::Middleware` as well as the `JkoApi::Strategies` modules won't exist, and neither will `warden`. You can [read up more here](https://github.com/hassox/warden/issues/117).
149
+
150
+ My recommendation for testing api endpoints is doing an integration test. With RSpec, just create your `spec/requests` folder, and add your spec for the endpoint you want to test.
151
+
152
+ If you need access to a mock warden object, you can add
153
+
154
+ ```ruby
155
+ require 'jko_api/test_helpers'
156
+ include JkoApi::TestHelpers
157
+ ```
158
+
159
+ ## Debugging notes
160
+
161
+ If you have a route that matches one of the API routes, and it's listed first, then Rails will match that route first. This might not be what you expected, so put your `JkoApi.routes` near the top to ensure that gets hit first.
162
+
92
163
 
93
164
  ## Contributing
94
165
 
@@ -0,0 +1,3 @@
1
+ class ApiApplicationController < ActionController::Base
2
+ include JkoApi::Controller
3
+ end
@@ -0,0 +1,7 @@
1
+ JkoApi.auth_setup do
2
+ Warden::OAuth2.configure do |config|
3
+ if JkoApi.configuration.strategy.bearer?
4
+ config.token_model = JkoApi.configuration.token_model
5
+ end
6
+ end
7
+ end
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["jeremywoertink@gmail.com"]
11
11
  spec.summary = %q{A Rails API gem}
12
12
  spec.description = %q{Some Rails API code written by JustinKo and ported to a badly written gem}
13
- spec.homepage = ""
13
+ spec.homepage = "https://github.com/jwoertink/jko_api"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.7"
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_dependency "responders", "2.1.0"
24
- spec.add_dependency "rails", ">= 4.2.0", "< 5"
23
+ spec.add_dependency "responders", "2.2.0"
24
+ spec.add_dependency "rails", ">= 5.0.0.beta3", "< 5.1"
25
+ spec.add_dependency "warden-oauth2"
25
26
  end
@@ -2,7 +2,9 @@ require "active_support/all"
2
2
  require "active_model/railtie"
3
3
  require "action_controller/railtie"
4
4
  require "responders"
5
+ require "warden-oauth2"
5
6
  require "jko_api/version"
7
+ require "jko_api/strategies"
6
8
  require "jko_api/class_descendants_builder"
7
9
  require "jko_api/constraints"
8
10
  require "jko_api/controller"
@@ -20,6 +22,7 @@ module JkoApi
20
22
  ACCEPT_HEADER_REGEX = /\Aapplication\/vnd\.api(\.v([0-9]))?\+json\z/
21
23
 
22
24
  mattr_accessor :configuration, instance_accessor: false
25
+ mattr_accessor :auth_initializer, instance_accessor: false
23
26
  mattr_reader :current_version_number, instance_reader: false
24
27
 
25
28
  def self.configure
@@ -30,7 +33,8 @@ module JkoApi
30
33
 
31
34
  def self.setup(base_controller)
32
35
  Util.stupid_hack!
33
- ClassDescendantsBuilder.build base_controller, level: max_version_number
36
+ ClassDescendantsBuilder.build(base_controller, upto: max_version_number)
37
+ @@auth_initializer.call
34
38
  end
35
39
 
36
40
  def self.activated?
@@ -66,4 +70,8 @@ module JkoApi
66
70
  JkoApi.versions(context, &block)
67
71
  end
68
72
  end
73
+
74
+ def self.auth_setup(&block)
75
+ self.auth_initializer = block
76
+ end
69
77
  end
@@ -2,45 +2,49 @@ module JkoApi
2
2
  class ClassDescendantsBuilder
3
3
  LEVEL_REGEX = /\d+/
4
4
 
5
- def self.build(base_class, level:)
5
+ def self.build(base_class, upto:)
6
6
  base_class.descendants.each do |descendant|
7
- new(descendant, level).build
7
+ new(descendant, upto).build
8
8
  end
9
9
  end
10
10
 
11
- def initialize(descendant, level)
12
- @descendant, @level = descendant, level
11
+ attr_reader :descendant, :upto
12
+
13
+ def initialize(descendant, upto)
14
+ @descendant, @upto = descendant, upto
13
15
  end
14
16
 
15
17
  def build
16
- initial_level.upto(@level - 1) do |level|
17
- build_descendant(level) unless descendant_defined?(level)
18
+ initial_level.upto(upto) do |level|
19
+ unless Module.qualified_const_defined?(swap_level(level.next))
20
+ build_descendant level
21
+ end
18
22
  end
19
23
  end
20
24
 
21
25
  private
22
26
 
23
- def descendant_defined?(level)
24
- !!swap_level(level.next).safe_constantize
25
- end
26
-
27
27
  def build_descendant(level)
28
- namespace(level.next).constantize.const_set(
28
+ namespace(level.next).const_set(
29
29
  swap_level(level.next).demodulize,
30
30
  Class.new(swap_level(level).constantize)
31
31
  )
32
32
  end
33
33
 
34
34
  def namespace(level)
35
- swap_level(level).deconstantize.presence || 'Object'
35
+ deconstantized = swap_level(level).deconstantize
36
+ unless Module.qualified_const_defined?(deconstantized)
37
+ Module.qualified_const_set deconstantized, Module.new
38
+ end
39
+ deconstantized.constantize
36
40
  end
37
41
 
38
42
  def swap_level(level)
39
- @descendant.name.sub LEVEL_REGEX, level.to_s
43
+ descendant.name.sub LEVEL_REGEX, level.to_s
40
44
  end
41
45
 
42
46
  def initial_level
43
- @descendant.name[LEVEL_REGEX].to_i
47
+ descendant.name[LEVEL_REGEX].to_i
44
48
  end
45
49
  end
46
50
  end
@@ -1,11 +1,21 @@
1
1
  module JkoApi
2
2
  class Configuration
3
3
 
4
- attr_accessor :base_controller, :api_namespace
4
+ attr_accessor :base_controller, :api_namespace, :use_authentication, :token_model
5
5
 
6
6
  def initialize
7
- @base_controller = Api::ApplicationController
7
+ @base_controller = ApiApplicationController
8
8
  @api_namespace = 'api'
9
+ @use_authentication = false
10
+ @strategy = :bearer
11
+ end
12
+
13
+ def authenticate?
14
+ use_authentication
15
+ end
16
+
17
+ def strategy
18
+ @strategy.to_s.inquiry
9
19
  end
10
20
 
11
21
  end
@@ -4,7 +4,12 @@ module JkoApi
4
4
 
5
5
  included do
6
6
  include JkoApi::ControllerHelpers
7
- skip_authentication # TODO: make this configurable
7
+
8
+ if JkoApi.configuration&.use_authentication
9
+ prepend_before_action :authenticate!
10
+ else
11
+ skip_authentication
12
+ end
8
13
 
9
14
  self.responder = JkoApi::Responder
10
15
  respond_to :json
@@ -15,5 +20,14 @@ module JkoApi
15
20
  def render_json_errors(status, message = status)
16
21
  render json: JkoApi::RequestError[status, message], status: status
17
22
  end
23
+
24
+ def warden
25
+ request.env['warden']
26
+ end
27
+
28
+ def authenticate!
29
+ token = warden.authenticate!
30
+ self.authenticated = !!token
31
+ end
18
32
  end
19
33
  end
@@ -5,7 +5,11 @@ module JkoApi
5
5
  included do
6
6
  class_attribute :authenticated
7
7
  self.authenticated = false
8
- append_before_action { raise 'setup authentication' unless authenticated }
8
+ append_before_action do
9
+ unless authenticated
10
+ render(json: {error: 'Authentication Failed'}, status: 401)
11
+ end
12
+ end
9
13
  end
10
14
 
11
15
  class_methods do
@@ -3,7 +3,12 @@ require 'rails/railtie'
3
3
  module JkoApi
4
4
  class Engine < ::Rails::Engine
5
5
  initializer "jko_api.configure_rails_initialization" do |app|
6
- app.middleware.use JkoApi::Middleware
6
+ app.middleware.use JkoApi::Middleware, only: proc { |env|
7
+ env['HTTP_ACCEPT'] && env['HTTP_ACCEPT'].include?('application/vnd.api') }
8
+
9
+ if JkoApi.configuration&.authenticate?
10
+ app.middleware.use "JkoApi::Strategies::#{JkoApi.configuration.strategy.to_s.capitalize}".constantize, only: proc { |env| env['HTTP_ACCEPT'] && env['HTTP_ACCEPT'].include?('application/vnd.api') }
11
+ end
7
12
  end
8
13
  end
9
14
  end
@@ -1,14 +1,17 @@
1
1
  module JkoApi
2
2
  class Middleware
3
- def initialize(app)
3
+ def initialize(app, options={})
4
4
  @app = app
5
+ @only = options[:only]
5
6
  end
6
7
 
7
8
  def call(env)
8
- if version_number = extract_version_number(env)
9
- ::JkoApi.current_version_number = version_number
10
- else
11
- ::JkoApi.reset
9
+ if @only && @only.call(env)
10
+ if version_number = extract_version_number(env)
11
+ ::JkoApi.current_version_number = version_number
12
+ else
13
+ ::JkoApi.reset
14
+ end
12
15
  end
13
16
  @app.call env
14
17
  end
@@ -0,0 +1 @@
1
+ require 'jko_api/strategies/bearer'
@@ -0,0 +1,26 @@
1
+ module JkoApi
2
+ module Strategies
3
+ class Bearer
4
+
5
+ def initialize(app, options ={})
6
+ @app = app
7
+ @only = options[:only]
8
+ @mgr = Warden::Manager.new(@app, options) do |config|
9
+ config.strategies.add :bearer, Warden::OAuth2::Strategies::Bearer
10
+ config.default_strategies :bearer
11
+ config.failure_app = Warden::OAuth2::FailureApp
12
+ end
13
+
14
+ @mgr
15
+ end
16
+
17
+ def call(env)
18
+ if @only && @only.call(env)
19
+ @mgr.call(env)
20
+ else
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module JkoApi
2
+ module TestHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Warden::Test::Mock
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module JkoApi
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jko_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Woertink
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-17 00:00:00.000000000 Z
11
+ date: 2016-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,34 +44,48 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 2.1.0
47
+ version: 2.2.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 2.1.0
54
+ version: 2.2.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rails
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 4.2.0
61
+ version: 5.0.0.beta3
62
62
  - - "<"
63
63
  - !ruby/object:Gem::Version
64
- version: '5'
64
+ version: '5.1'
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - ">="
70
70
  - !ruby/object:Gem::Version
71
- version: 4.2.0
71
+ version: 5.0.0.beta3
72
72
  - - "<"
73
73
  - !ruby/object:Gem::Version
74
- version: '5'
74
+ version: '5.1'
75
+ - !ruby/object:Gem::Dependency
76
+ name: warden-oauth2
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
75
89
  description: Some Rails API code written by JustinKo and ported to a badly written
76
90
  gem
77
91
  email:
@@ -85,7 +99,8 @@ files:
85
99
  - LICENSE.txt
86
100
  - README.md
87
101
  - Rakefile
88
- - app/controllers/api/application_controller.rb
102
+ - app/controllers/api_application_controller.rb
103
+ - config/initializers/warden.rb
89
104
  - jko_api.gemspec
90
105
  - lib/jko_api.rb
91
106
  - lib/jko_api/class_descendants_builder.rb
@@ -97,10 +112,13 @@ files:
97
112
  - lib/jko_api/middleware.rb
98
113
  - lib/jko_api/request_error.rb
99
114
  - lib/jko_api/responder.rb
115
+ - lib/jko_api/strategies.rb
116
+ - lib/jko_api/strategies/bearer.rb
117
+ - lib/jko_api/test_helpers.rb
100
118
  - lib/jko_api/util.rb
101
119
  - lib/jko_api/version.rb
102
120
  - lib/jko_api/versioning.rb
103
- homepage: ''
121
+ homepage: https://github.com/jwoertink/jko_api
104
122
  licenses:
105
123
  - MIT
106
124
  metadata: {}
@@ -120,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
138
  version: '0'
121
139
  requirements: []
122
140
  rubyforge_project:
123
- rubygems_version: 2.4.3
141
+ rubygems_version: 2.5.1
124
142
  signing_key:
125
143
  specification_version: 4
126
144
  summary: A Rails API gem
@@ -1,3 +0,0 @@
1
- class Api::ApplicationController < ActionController::Base
2
- include JkoApi::Controller
3
- end