jko_api 0.1.3 → 0.2.9

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
- SHA1:
3
- metadata.gz: 3711a73e0bd2f3bb5186bf24f11d4530b9bbeb6c
4
- data.tar.gz: e3a28ae1fc62c36cf5a3b77ee0f6820873fb9a75
2
+ SHA256:
3
+ metadata.gz: 88aaa8b5e8d2dbda590b75a03aa6a860f982fe2be6bab9b2cd85ccc2a7e1b579
4
+ data.tar.gz: d3a107e9504ab41724cee758bb4f987f0c47de5b8f04a1a29943a4c1b79f1dd5
5
5
  SHA512:
6
- metadata.gz: c6adaaad9e11e56f1971934b7522dae6cc7376e18ddae3b3358202ee73dc85452327d5eab3a62c594e05f525ced2320ff1df5b1acbcf03b8e8ce0faedcbadc6a
7
- data.tar.gz: 26a75d36b762ffa83d8f74f93374b5a843f73fdc9d1c80e3f969afdd68722d2055e9d527e239b722f70477304680f2de002490f19c041bace08d668cd4816bc3
6
+ metadata.gz: fd91bf624a79ba36727c6ff46964b785aa250d2704958a54946e2a777a70f2317a24f4730a2de0f87df2cdd68f2633db5a0208488a7c0996c959b4e43e0fbdfd
7
+ data.tar.gz: f2426c6e85c5febd3abcc5317f324f97b790ad8bed546d57bec5051ebef4e286968334821365732788c0cdd16401e95a4c2df1af8d862a4c9d93bb3d1c07b00b
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")
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.7"
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"
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_dependency "responders", ">= 2.4.0"
24
+ spec.add_dependency "rails", ">= 4.2.7"
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,17 +22,22 @@ 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
27
+ @@versioning = nil
24
28
 
25
29
  def self.configure
26
30
  self.configuration ||= Configuration.new
27
31
  yield(configuration)
28
- setup(configuration.base_controller)
32
+ self.configuration
29
33
  end
30
34
 
31
- def self.setup(base_controller)
32
- Util.stupid_hack!
33
- ClassDescendantsBuilder.build base_controller, level: max_version_number
35
+ def self.complete_setup(base_controller)
36
+ if Rails.version.to_i < 5
37
+ Util.stupid_hack!
38
+ end
39
+ ClassDescendantsBuilder.build(base_controller, upto: max_version_number)
40
+ @@auth_initializer.call
34
41
  end
35
42
 
36
43
  def self.activated?
@@ -65,5 +72,11 @@ module JkoApi
65
72
  context.scope(module: JkoApi.configuration.api_namespace, constraints: JkoApi::Constraints, defaults: {format: :json}) do
66
73
  JkoApi.versions(context, &block)
67
74
  end
75
+ complete_setup(configuration.base_controller)
76
+ context
77
+ end
78
+
79
+ def self.auth_setup(&block)
80
+ self.auth_initializer = block
68
81
  end
69
82
  end
@@ -2,45 +2,79 @@ 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.const_defined?(swap_level(level))
20
+ build_descendant level.pred
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(
29
- swap_level(level.next).demodulize,
30
- Class.new(swap_level(level).constantize)
28
+ namespace(level).const_set(
29
+ swap_level(level).demodulize,
30
+ Class.new(swap_level(level.next).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 qualified_const_defined?(deconstantized)
37
+ 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
48
+ end
49
+
50
+ # Pulled from Rails 4.2 source
51
+ def qualified_const_defined?(path)
52
+ raise NameError, "#{path.inspect} is not a valid constant name!" unless /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path
53
+
54
+ names = path.to_s.split('::')
55
+ names.shift if names.first.empty?
56
+
57
+ # We can't use defined? because it will invoke const_missing for the parent
58
+ # of the name we are checking.
59
+ names.inject(Module) do |mod, name|
60
+ return false unless mod.const_defined?(name, false)
61
+ mod.const_get name
62
+ end
63
+ return true
64
+ end
65
+
66
+ def qualified_const_set(path, value)
67
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
68
+ const_name = path.demodulize
69
+ mod_name = path.deconstantize
70
+ mod = mod_name.empty? ? self : qualified_const_get(mod_name)
71
+ mod.const_set(const_name, value) unless mod.const_defined?(const_name)
72
+ end
73
+
74
+ def qualified_const_get(path)
75
+ path.split('::').inject(Module) do |mod, name|
76
+ mod.const_get(name)
77
+ end
44
78
  end
45
79
  end
46
80
  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,6 +1,8 @@
1
1
  module JkoApi
2
2
  class Util
3
3
 
4
+ # This was a hack needed in rails 4.2
5
+ # I'm keeping the code for backwards compatibility (for now)
4
6
  def self.stupid_hack!
5
7
  Rails.application.reload_routes!
6
8
  eager_load_api_controllers
@@ -1,3 +1,3 @@
1
1
  module JkoApi
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.9"
3
3
  end
metadata CHANGED
@@ -1,77 +1,85 @@
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.9
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: 2020-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '0'
20
20
  type: :development
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: '1.7'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: responders
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 2.1.0
47
+ version: 2.4.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.4.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
62
- - - "<"
63
- - !ruby/object:Gem::Version
64
- version: '5'
61
+ version: 4.2.7
65
62
  type: :runtime
66
63
  prerelease: false
67
64
  version_requirements: !ruby/object:Gem::Requirement
68
65
  requirements:
69
66
  - - ">="
70
67
  - !ruby/object:Gem::Version
71
- version: 4.2.0
72
- - - "<"
68
+ version: 4.2.7
69
+ - !ruby/object:Gem::Dependency
70
+ name: warden-oauth2
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
73
81
  - !ruby/object:Gem::Version
74
- version: '5'
82
+ version: '0'
75
83
  description: Some Rails API code written by JustinKo and ported to a badly written
76
84
  gem
77
85
  email:
@@ -85,7 +93,8 @@ files:
85
93
  - LICENSE.txt
86
94
  - README.md
87
95
  - Rakefile
88
- - app/controllers/api/application_controller.rb
96
+ - app/controllers/api_application_controller.rb
97
+ - config/initializers/warden.rb
89
98
  - jko_api.gemspec
90
99
  - lib/jko_api.rb
91
100
  - lib/jko_api/class_descendants_builder.rb
@@ -97,10 +106,13 @@ files:
97
106
  - lib/jko_api/middleware.rb
98
107
  - lib/jko_api/request_error.rb
99
108
  - lib/jko_api/responder.rb
109
+ - lib/jko_api/strategies.rb
110
+ - lib/jko_api/strategies/bearer.rb
111
+ - lib/jko_api/test_helpers.rb
100
112
  - lib/jko_api/util.rb
101
113
  - lib/jko_api/version.rb
102
114
  - lib/jko_api/versioning.rb
103
- homepage: ''
115
+ homepage: https://github.com/jwoertink/jko_api
104
116
  licenses:
105
117
  - MIT
106
118
  metadata: {}
@@ -119,8 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
131
  - !ruby/object:Gem::Version
120
132
  version: '0'
121
133
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.4.3
134
+ rubygems_version: 3.1.2
124
135
  signing_key:
125
136
  specification_version: 4
126
137
  summary: A Rails API gem
@@ -1,3 +0,0 @@
1
- class Api::ApplicationController < ActionController::Base
2
- include JkoApi::Controller
3
- end