nginx_omniauth_adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3908b68bd6b56d6a923bca0dc36763e55645a03
4
+ data.tar.gz: 4aaf9ce2fdb95257094210060b44fdb9e533d163
5
+ SHA512:
6
+ metadata.gz: 2b42a2d07c831778ea2b9c96e17e928cf2c5c0891edac619d89dfa74d27a14a0994469b2d8fb08e1082b3ce648659e9fbfdc7dcffe461679196621717199dc08
7
+ data.tar.gz: 366cf4d330531166973f61168e0ccbebf6cbcd7939e5ebc2dd91f87d15aa6cca0ecbca9b18463ce0e44b5b5e27406b6e3c034a889e2ac904dda3185fc9386de8
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.10.6
data/Dockerfile ADDED
@@ -0,0 +1,18 @@
1
+ FROM quay.io/sorah/rbenv:2.2
2
+ MAINTAINER sorah
3
+
4
+ RUN mkdir -p /app
5
+
6
+ ADD Gemfile* /tmp/
7
+ ADD nginx_omniauth_adapter.gemspec /tmp/
8
+ RUN mkdir -p /tmp/lib/nginx_omniauth_adapter
9
+ ADD lib/nginx_omniauth_adapter/version.rb /tmp/lib/nginx_omniauth_adapter/version.rb
10
+ RUN cd /tmp && bundle install -j4 --path vendor/bundle --without 'development test'
11
+
12
+ WORKDIR /app
13
+ ADD . /app
14
+ RUN cp -a /tmp/.bundle /tmp/vendor /app/
15
+
16
+ EXPOSE 8080
17
+ ENV RACK_ENV=production
18
+ CMD ["bundle", "exec", "rackup", "-p", "8080", "-o", "0.0.0.0", "config.ru"]
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nginx_omniauth_adapter.gemspec
4
+ gemspec
5
+
6
+ gem 'omniauth-github'
7
+ gem 'omniauth-google-oauth2'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Shota Fukumori (sora_h)
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,131 @@
1
+ # NginxOmniauthAdapter - Use omniauth for nginx `auth_request`
2
+
3
+ Use [omniauth](https://github.com/intridea/omniauth) for your nginx's authentication via ngx_http_auth_request_module.
4
+
5
+ NginxOmniauthAdapter provides small Rack app (built with Sinatra) for `auth_request`.
6
+
7
+ ## Prerequisite
8
+
9
+ - nginx with ngx_http_auth_request_module
10
+
11
+ ## Quick example
12
+
13
+ ```
14
+ $ bundle install
15
+
16
+ $ cd example/
17
+ $ foreman start
18
+ ```
19
+
20
+ http://ngx-auth-test.127.0.0.1.xip.io:18080/
21
+
22
+ (make sure to have nginx on your PATH)
23
+
24
+ ## Usage
25
+
26
+ ### Steps
27
+
28
+ 1. Start adapter app with proper configuration
29
+ 2. enable `auth_request` and add some endpoints on nginx
30
+ - See `example/nginx-site.conf` for nginx configuration.
31
+
32
+ ### Running with Rubygems
33
+
34
+ ```ruby
35
+ # Gemfile
36
+ gem 'nginx_omniauth_adapter'
37
+ ```
38
+
39
+ Then write `config.ru` then deploy it. (see ./config.ru for example)
40
+
41
+ ### Using docker
42
+
43
+ TBD
44
+
45
+ ## Configuration
46
+
47
+ environment variable is available only on included config.ru (or Docker image).
48
+
49
+ - `:providers`: omniauth provider names.
50
+ - `:secret` `$NGX_OMNIAUTH_SESSION_SECRET`: Rack session secret. Should be set when not on dev mode
51
+ - `:host` `$NGX_OMNIAUTH_HOST`: URL of adapter. This is used for redirection. Should include protocol (e.g. `http://example.com`.)
52
+ - If this is not specified, adapter will perform redirect using given `Host` header.
53
+ - `:allowed_app_callback_url` `$NGX_OMNIAUTH_ALLOWED_APP_CALLBACK_URL` (regexp): If specified, URL only matches to this are allowed for app callback url.
54
+ - `:allowed_back_to_url` `$NGX_OMNIAUTH_ALLOWED_BACK_TO_URL` (regexp): If specified, URL only matches to this are allowed for back_to url.
55
+ - `:app_refresh_interval` `NGX_OMNIAUTH_APP_REFRESH_INTERVAL` (integer): Interval to require refresh session cookie on app domain (in second, default 1 day).
56
+ - `:adapter_refresh_interval` `NGX_OMNIAUTH_ADAPTER_REFRESH_INTERVAL` (integer): Interval to require re-logging in on adapter domain (in second, default 3 days).
57
+
58
+ ### Included config.ru (or Docker)
59
+
60
+ You can set configuration via environment variables.
61
+
62
+ The following variables are only available on included config.ru:
63
+
64
+ - `$NGX_OMNIAUTH_SESSION_COOKIE_NAME`: session cookie name (default `ngx_oauth`)
65
+ - `$NGX_OMNIAUTH_SESSION_COOKIE_TIMEOUT`: session cookie expiry (default 3 days)
66
+ - `$NGX_OMNIAUTH_DEV=1` or `$RACK_ENV=development`
67
+ - enable dev mode (omniauth developer provider)
68
+ - github provider
69
+ - `$NGX_OMNIAUTH_GITHUB_KEY`, `$NGX_OMNIAUTH_GITHUB_SECRET`: application key + secret.
70
+ - `$NGX_OMNIAUTH_GITHUB_HOST`: (optional) Set if you'd like to use GitHub Enterprise instance (e.g. `https://YOUR-GITHUB-ENTERPRISE`)
71
+ - `$NGX_OMNIAUTH_GITHUB_TEAMS`: (optional) Restrict to specified teams (e.g. `awesomeorganization/owners`)
72
+ - google_oauth2 provider
73
+ - `$NGX_OMNIAUTH_GOOGLE_KEY`, `$NGX_OMNIAUTH_GOOGLE_SECRET`: oauth2 key + secret.
74
+ - `$NGX_OMNIAUTH_GOOGLE_HD`: (optional) Restrict to specified hosted domain (Google Apps Domain).
75
+
76
+
77
+
78
+ ### Manually (Rack)
79
+
80
+ If you're going to write `config.ru` from scratch, make sure:
81
+
82
+ - OmniAuth is included in middleware stack
83
+ - Rack session is enabled in middleware stack
84
+
85
+ Then run:
86
+
87
+ ``` ruby
88
+ run NginxOmniauthAdapter.app(
89
+ providers: %i(developer),
90
+ secret: secret_base64, # optional
91
+ # ... (set more configuration, see above variable list)
92
+ )
93
+ ```
94
+
95
+ ## How it works
96
+
97
+ 1. _browser_ access to restricted area (where `auth_request` has enabled)
98
+ 2. _nginx_ sends subrequest to `/_auth/challenge`. It will be proxied to _adapter app_ (`GET /test`)
99
+ 3. _adapter app_ `/test` returns 401 when _request (browser)_ doesn't have valid cookie
100
+ 4. _nginx_ handles 401 with `error_page`, so do internal redirection (`/_auth/initiate`)
101
+ 5. _nginx_ handles `/_auth/initiate`. It will be proxied to _adapter app_ `GET /initiate`.
102
+ - Also _nginx_ passes some information for callback to _adapter app._
103
+ - `x-ngx-oauth-initiate-back-to` URL to back after logged in
104
+ - `x-ngx-oauth-initiate-callback` URL that proxies to _adapter app_ `/callback`. This must be same domain to _backend app_ for cookie.
105
+ 6. _adapter app_ `GET /initiate` redirects to `/auth/:provider`.
106
+ 7. _Browser_ do some authenticate in _adapter app_ with Omniauth.
107
+ 8. _adapter app's_ omniauth callback sets valid session, then redirects to `/_auth/callback`, where specified at `x-ngx-oauth-initiate-callback`.
108
+ - _Adapter app_ gives GET parameter named `session` on redirect. It contains encrypted session.
109
+ 9. _nginx_ handles `/_auth/callback`. It will be proxied to _adapter app_ `/callback`.
110
+ - This decrypts given encrypted session string and set to cookie.
111
+ - Then redirect to `x-ngx-oauth-initiate-back-to`.
112
+ 10. _browser_ backs to URL where attempted to access first, at step 1.
113
+ 11. _nginx_ sends auth subrequest to _backend app_ `/test`.
114
+ 12. _backend app_ `/test` returns 200, because request has valid session cookie.
115
+ 13. _nginx_ returns response as usual.
116
+
117
+ ## Development
118
+
119
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
120
+
121
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
122
+
123
+ ## Contributing
124
+
125
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sorah/nginx_omniauth_adapter.
126
+
127
+
128
+ ## License
129
+
130
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
131
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/config.ru ADDED
@@ -0,0 +1,106 @@
1
+ require 'nginx_omniauth_adapter'
2
+ require 'omniauth'
3
+ require 'open-uri'
4
+ require 'json'
5
+
6
+ dev = ENV['NGX_OMNIAUTH_DEV'] == '1' || ENV['RACK_ENV'] == 'development'
7
+ test = ENV['RACK_ENV'] == 'test'
8
+
9
+ if test
10
+ dev = true
11
+ warn 'TEST MODE'
12
+ OmniAuth.config.test_mode = true
13
+ OmniAuth.config.mock_auth[:developer] = {provider: 'developer', uid: '42', info: {}}
14
+ end
15
+
16
+ if !dev && !ENV['NGX_OMNIAUTH_SESSION_SECRET']
17
+ raise 'You should specify $NGX_OMNIAUTH_SESSION_SECRET'
18
+ end
19
+
20
+ allowed_app_callback_url = if ENV['NGX_OMNIAUTH_ALLOWED_APP_CALLBACK_URL']
21
+ Regexp.new(ENV['NGX_OMNIAUTH_ALLOWED_APP_CALLBACK_URL'])
22
+ else
23
+ nil
24
+ end
25
+
26
+ allowed_back_to_url = if ENV['NGX_OMNIAUTH_ALLOWED_BACK_TO_URL']
27
+ Regexp.new(ENV['NGX_OMNIAUTH_ALLOWED_BACK_TO_URL'])
28
+ else
29
+ nil
30
+ end
31
+
32
+ use(
33
+ Rack::Session::Cookie,
34
+ key: ENV['NGX_OMNIAUTH_SESSION_COOKIE_NAME'] || 'ngx_oauth',
35
+ expire_after: ENV['NGX_OMNIAUTH_SESSION_COOKIE_TIMEOUT'] ? ENV['NGX_OMNIAUTH_SESSION_COOKIE_TIMEOUT'].to_i : (60 * 60 * 24 * 3),
36
+ secret: ENV['NGX_OMNIAUTH_SESSION_SECRET'] || 'ngx_oauth_secret_dev',
37
+ old_secret: ENV['NGX_OMNIAUTH_SESSION_SECRET_OLD'],
38
+ )
39
+
40
+ providers = []
41
+
42
+ gh_teams = ENV['NGX_OMNIAUTH_GITHUB_TEAMS'] && ENV['NGX_OMNIAUTH_GITHUB_TEAMS'].split(/[, ]/)
43
+
44
+ use OmniAuth::Builder do
45
+ if ENV['NGX_OMNIAUTH_GITHUB_KEY'] && ENV['NGX_OMNIAUTH_GITHUB_SECRET']
46
+ require 'omniauth-github'
47
+ gh_client_options = {}
48
+ if ENV['NGX_OMNIAUTH_GITHUB_HOST']
49
+ gh_client_options[:site] = "#{ENV['NGX_OMNIAUTH_GITHUB_HOST']}/api/v3"
50
+ gh_client_options[:authorize_url] = "#{ENV['NGX_OMNIAUTH_GITHUB_HOST']}/login/oauth/authorize"
51
+ gh_client_options[:token_url] = "#{ENV['NGX_OMNIAUTH_GITHUB_HOST']}/login/oauth/access_token"
52
+ end
53
+
54
+ gh_scope = ''
55
+ if ENV['NGX_OMNIAUTH_GITHUB_TEAMS']
56
+ gh_scope = 'read:org'
57
+ end
58
+
59
+ provider :github, ENV['NGX_OMNIAUTH_GITHUB_KEY'], ENV['NGX_OMNIAUTH_GITHUB_SECRET'], client_options: gh_client_options, scope: gh_scope
60
+ providers << :github
61
+ end
62
+
63
+ if ENV['NGX_OMNIAUTH_GOOGLE_KEY'] && ENV['NGX_OMNIAUTH_GOOGLE_SECRET']
64
+ require 'omniauth-google-oauth2'
65
+ provider :google_oauth2, ENV['NGX_OMNIAUTH_GOOGLE_KEY'], ENV['NGX_OMNIAUTH_GOOGLE_SECRET'], hd: ENV['NGX_OMNIAUTH_GOOGLE_HD']
66
+ providers << :google_oauth2
67
+ end
68
+
69
+ if dev
70
+ provider :developer
71
+ providers << :developer
72
+ end
73
+ end
74
+
75
+ run NginxOmniauthAdapter.app(
76
+ providers: providers,
77
+ secret: ENV['NGX_OMNIAUTH_SECRET'],
78
+ host: ENV['NGX_OMNIAUTH_HOST'],
79
+ allowed_app_callback_url: allowed_app_callback_url,
80
+ allowed_back_to_url: allowed_back_to_url,
81
+ app_refresh_interval: ENV['NGX_OMNIAUTH_APP_REFRESH_INTERVAL'] && ENV['NGX_OMNIAUTH_APP_REFRESH_INTERVAL'].to_i,
82
+ adapter_refresh_interval: ENV['NGX_OMNIAUTH_ADAPTER_REFRESH_INTERVAL'] && ENV['NGX_OMNIAUTH_APP_REFRESH_INTERVAL'].to_i,
83
+ policy_proc: proc {
84
+ if gh_teams && current_user[:provider] == 'github'
85
+ unless (current_user_data[:gh_teams] || []).any? { |team| gh_teams.include?(team) }
86
+ next false
87
+ end
88
+ end
89
+
90
+ true
91
+ },
92
+ on_login_proc: proc {
93
+ auth = env['omniauth.auth']
94
+ case auth[:provider]
95
+ when 'github'
96
+ if gh_teams
97
+ api_host = ENV['NGX_OMNIAUTH_GITHUB_HOST'] ? "#{ENV['NGX_OMNIAUTH_GITHUB_HOST']}/api/v3" : "https://api.github.com"
98
+ current_user_data[:gh_teams] = open("#{api_host}/user/teams", 'Authorization' => "token #{auth['credentials']['token']}") { |io|
99
+ JSON.parse(io.read).map {|_| "#{_['organization']['login']}/#{_['slug']}" }.select { |team| gh_teams.include?(team) }
100
+ }
101
+ end
102
+ end
103
+
104
+ true
105
+ },
106
+ )
data/example/Procfile ADDED
@@ -0,0 +1,3 @@
1
+ nginx: nginx -c `pwd`/nginx.conf
2
+ adapter: bundle exec env RACK_ENV=development NGX_OMNIAUTH_HOST=http://ngx-auth.127.0.0.1.xip.io:18080 rackup -p 18081 -o 127.0.0.1 ../config.ru
3
+ app: bundle exec ruby test_backend.rb -p 18082 -o 127.0.0.1
@@ -0,0 +1,73 @@
1
+ # vim: ft=nginx
2
+
3
+ # nginx_omniauth_adapter app
4
+ upstream auth_adapter {
5
+ server 127.0.0.1:18081 fail_timeout=0;
6
+ }
7
+ server {
8
+ listen 127.0.0.1:18080;
9
+ server_name ngx-auth.127.0.0.1.xip.io;
10
+
11
+ location / {
12
+ proxy_pass http://auth_adapter;
13
+ proxy_set_header Host $http_host;
14
+ }
15
+ }
16
+
17
+ # server where you want to restrict access
18
+ upstream app {
19
+ server 127.0.0.1:18082 fail_timeout=0;
20
+ }
21
+ server {
22
+ listen 127.0.0.1:18080;
23
+ server_name ngx-auth-test.127.0.0.1.xip.io;
24
+
25
+ # Restricted area
26
+ location / {
27
+ auth_request /_auth/challenge;
28
+ # Trick - do internal redirection when auth_request says "need auth".
29
+ error_page 401 = /_auth/initiate;
30
+
31
+ # Receive user info from adapter
32
+ auth_request_set $ngx_oauth_provider $upstream_http_x_ngx_oauth_provider;
33
+ auth_request_set $ngx_oauth_user $upstream_http_x_ngx_oauth_user;
34
+ auth_request_set $ngx_oauth_info $upstream_http_x_ngx_oauth_info;
35
+ proxy_set_header x-ngx-oauth-provider $ngx_oauth_provider;
36
+ proxy_set_header x-ngx-oauth-user $ngx_oauth_user;
37
+ proxy_set_header x-ngx-oauth-info $ngx_oauth_info;
38
+
39
+ # pass to backend application as usual as you do.
40
+ proxy_pass http://app;
41
+ }
42
+
43
+ # STEP 1, Internal endpoint: test still session is valid (adapter: GET /test)
44
+ location = /_auth/challenge {
45
+ internal;
46
+
47
+ proxy_pass_request_body off;
48
+ proxy_set_header Content-Length "";
49
+ proxy_set_header Host $http_host;
50
+
51
+ proxy_pass http://auth_adapter/test;
52
+ }
53
+
54
+ # STEP 2, Internal endpoint: Initiate authentication. Will redirect to adapter for omniauth sequence. (adapter: GET /initiate)
55
+ location = /_auth/initiate {
56
+ internal;
57
+ proxy_pass_request_body off;
58
+ proxy_set_header Content-Length "";
59
+ proxy_set_header Host $http_host;
60
+ proxy_set_header x-ngx-oauth-initiate-back-to http://$http_host$request_uri;
61
+ proxy_set_header x-ngx-oauth-initiate-callback http://$http_host/_auth/callback;
62
+ proxy_pass http://auth_adapter/initiate;
63
+ }
64
+
65
+ # STEP 3, adapter will back here when authentication succeeded. proxy_pass to adapter to set session cookie.
66
+ location = /_auth/callback {
67
+ auth_request off;
68
+
69
+ proxy_set_header Host $http_host;
70
+
71
+ proxy_pass http://auth_adapter/callback;
72
+ }
73
+ }
@@ -0,0 +1,17 @@
1
+ # vim: ft=nginx
2
+ worker_processes 1;
3
+ daemon off;
4
+
5
+ error_log stderr;
6
+
7
+ events {
8
+ worker_connections 1024;
9
+ }
10
+
11
+ http {
12
+ default_type application/octet-stream;
13
+
14
+ server_names_hash_bucket_size 128;
15
+
16
+ include nginx-site.conf;
17
+ }
@@ -0,0 +1,21 @@
1
+ require 'sinatra'
2
+ require 'json'
3
+
4
+ get '/' do
5
+ content_type :text
6
+
7
+ {
8
+ provider: request.env['HTTP_X_NGX_OAUTH_PROVIDER'],
9
+ user: request.env['HTTP_X_NGX_OAUTH_USER'],
10
+ info: JSON.parse(request.env['HTTP_X_NGX_OAUTH_INFO'].unpack('m*')[0]),
11
+ }.to_json
12
+ end
13
+
14
+ get '/hello' do
15
+ content_type :text
16
+ 'hola'
17
+ end
18
+
19
+ get '/unauthorized' do
20
+ [401, {'Content-Type' => 'text/plain'}, '401 :(']
21
+ end
@@ -0,0 +1,281 @@
1
+ require 'sinatra/base'
2
+ require 'uri'
3
+ require 'time'
4
+ require 'openssl'
5
+ require 'json'
6
+
7
+ module NginxOmniauthAdapter
8
+ class App < Sinatra::Base
9
+ CONTEXT_RACK_ENV_NAME = 'nginx-oauth2-adapter'.freeze
10
+ SESSION_PASS_CIPHER_ALGORITHM = 'aes-256-gcm'.freeze
11
+
12
+ set :root, File.expand_path(File.join(__dir__, '..', '..', 'app'))
13
+
14
+ def self.initialize_context(config)
15
+ {}.tap do |ctx|
16
+ ctx[:config] = config
17
+ end
18
+ end
19
+
20
+ def self.rack(config={})
21
+ klass = self
22
+
23
+ context = initialize_context(config)
24
+ app = lambda { |env|
25
+ env[CONTEXT_RACK_ENV_NAME] = context
26
+ klass.call(env)
27
+ }
28
+ end
29
+
30
+ helpers do
31
+ def context
32
+ request.env[CONTEXT_RACK_ENV_NAME]
33
+ end
34
+
35
+ def adapter_config
36
+ context[:config]
37
+ end
38
+
39
+ def adapter_host
40
+ adapter_config[:host]
41
+ end
42
+
43
+ def providers
44
+ adapter_config[:providers]
45
+ end
46
+
47
+ def allowed_back_to_url
48
+ adapter_config[:allowed_back_to_url] || /./
49
+ end
50
+
51
+ def allowed_app_callback_url
52
+ adapter_config[:allowed_app_callback_url] || /./
53
+ end
54
+
55
+ def on_login_proc
56
+ adapter_config[:on_login_proc] || proc { true }
57
+ end
58
+
59
+ def policy_proc
60
+ adapter_config[:policy_proc] || proc { true }
61
+ end
62
+
63
+ def default_back_to
64
+ # TODO:
65
+ '/'
66
+ end
67
+
68
+ def sanitized_back_to_param
69
+ p params[:back_to]
70
+ if allowed_back_to_url === params[:back_to]
71
+ params[:back_to]
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ def sanitized_app_callback_param
78
+ p params[:callback]
79
+ if allowed_app_callback_url === params[:callback]
80
+ params[:callback]
81
+ else
82
+ nil
83
+ end
84
+ end
85
+
86
+ def current_user
87
+ session[:user]
88
+ end
89
+
90
+ def current_user_data
91
+ session[:user_data] ||= {}
92
+ end
93
+
94
+ def current_authorized_at
95
+ session[:authorized_at] && Time.xmlschema(session[:authorized_at])
96
+ end
97
+
98
+ def current_logged_in_at
99
+ session[:logged_in_at] && Time.xmlschema(session[:logged_in_at])
100
+ end
101
+
102
+ def app_refresh_interval
103
+ adapter_config[:app_refresh_interval] || (60 * 60 * 24)
104
+ end
105
+
106
+ def adapter_refresh_interval
107
+ adapter_config[:adapter_refresh_interval] || (60 * 60 * 24 * 30)
108
+ end
109
+
110
+ def app_authorization_expired?
111
+ app_refresh_interval && current_user && (Time.now - current_authorized_at) > app_refresh_interval
112
+ end
113
+
114
+ def adapter_authorization_expired?
115
+ adapter_refresh_interval && current_user && (Time.now - current_logged_in_at) > adapter_refresh_interval
116
+ end
117
+
118
+ def update_session!(auth = nil)
119
+ unless session[:app_callback]
120
+ raise '[BUG] app_callback is missing'
121
+ end
122
+
123
+ common_session = {
124
+ logged_in_at: session[:logged_in_at],
125
+ user_data: current_user_data,
126
+ }
127
+
128
+ if auth
129
+ common_session[:user] = {
130
+ uid: auth[:uid],
131
+ info: auth[:info],
132
+ provider: auth[:provider],
133
+ }
134
+ else
135
+ common_session[:user] = session[:user]
136
+ end
137
+
138
+ adapter_session = common_session.merge(
139
+ side: :adapter,
140
+ )
141
+
142
+ app_session = common_session.merge(
143
+ side: :app,
144
+ back_to: session.delete(:back_to),
145
+ authorized_at: Time.now.xmlschema,
146
+ )
147
+
148
+ session.merge!(adapter_session)
149
+
150
+ session_param = encrypt_session_param(app_session)
151
+ redirect "#{session.delete(:app_callback)}?session=#{session_param}"
152
+ end
153
+
154
+ def secret_key
155
+ context[:secret_key] ||= begin
156
+ if adapter_config[:secret]
157
+ adapter_config[:secret].unpack('m*')[0]
158
+ else
159
+ cipher = OpenSSL::Cipher.new(SESSION_PASS_CIPHER_ALGORITHM)
160
+ warn "WARN: :secret not set; generating randomly."
161
+ warn " If you'd like to persist, set `openssl rand -base64 #{cipher.key_len}` . Note that you have to keep it secret."
162
+
163
+ OpenSSL::Random.random_bytes(cipher.key_len)
164
+ end
165
+ end
166
+ end
167
+
168
+ def encrypt_session_param(session_param)
169
+ iv = nil
170
+ cipher ||= OpenSSL::Cipher.new(SESSION_PASS_CIPHER_ALGORITHM).tap do |c|
171
+ c.encrypt
172
+ c.key = secret_key
173
+ c.iv = iv = c.random_iv
174
+ c.auth_data = ''
175
+ end
176
+
177
+ plaintext = Marshal.dump(session_param)
178
+
179
+ ciphertext = cipher.update(plaintext)
180
+ ciphertext << cipher.final
181
+
182
+ URI.encode_www_form_component([{
183
+ "iv" => [iv].pack('m*'),
184
+ "data" => [ciphertext].pack('m*'),
185
+ "tag" => [cipher.auth_tag].pack('m*'),
186
+ }.to_json].pack('m*'))
187
+ end
188
+
189
+ def decrypt_session_param(raw_data)
190
+ data = JSON.parse(raw_data.unpack('m*')[0])
191
+
192
+ cipher ||= OpenSSL::Cipher.new(SESSION_PASS_CIPHER_ALGORITHM).tap do |c|
193
+ c.decrypt
194
+ c.key = secret_key
195
+ c.iv = data['iv'].unpack('m*')[0]
196
+ c.auth_data = ''
197
+ c.auth_tag = data['tag'].unpack('m*')[0]
198
+ end
199
+
200
+ plaintext = cipher.update(data['data'].unpack('m*')[0])
201
+ plaintext << cipher.final
202
+
203
+ Marshal.load(plaintext)
204
+ end
205
+
206
+ end
207
+
208
+ get '/' do
209
+ content_type :text
210
+ "NginxOmniauthAdapter #{NginxOmniauthAdapter::VERSION}"
211
+ end
212
+
213
+ get '/test' do
214
+ unless current_user
215
+ halt 401
216
+ end
217
+
218
+ if app_authorization_expired?
219
+ halt 401
220
+ end
221
+
222
+ unless instance_eval(&policy_proc)
223
+ halt 403
224
+ end
225
+
226
+ headers(
227
+ 'x-ngx-oauth-provider' => current_user[:provider],
228
+ 'x-ngx-oauth-user' => current_user[:uid],
229
+ 'x-ngx-oauth-info' => [current_user[:info].to_json].pack('m*'),
230
+ )
231
+
232
+ content_type :text
233
+ 'ok'.freeze
234
+ end
235
+
236
+ get '/initiate' do
237
+ back_to = URI.encode_www_form_component(request.env['HTTP_X_NGX_OAUTH_INITIATE_BACK_TO'])
238
+ callback = URI.encode_www_form_component(request.env['HTTP_X_NGX_OAUTH_INITIATE_CALLBACK'])
239
+
240
+ if back_to == '' || callback == '' || back_to.nil? || callback.nil?
241
+ halt 400, {'Content-Type' => 'text/plain'}, 'x-ngx-oauth-initiate-back-to and x-ngx-oauth-initiate-callback header are required'
242
+ end
243
+
244
+ redirect "#{adapter_host}/auth?back_to=#{back_to}&callback=#{callback}"
245
+ end
246
+
247
+ get '/auth' do
248
+ # TODO: choose provider
249
+ session[:back_to] = sanitized_back_to_param
250
+ session[:app_callback] = sanitized_app_callback_param
251
+ p [:auth, session]
252
+
253
+ if current_user && !adapter_authorization_expired?
254
+ p [:auth, :update]
255
+ update_session!
256
+ else
257
+ p [:auth, :redirect]
258
+ redirect "#{adapter_host}/auth/#{providers[0]}"
259
+ end
260
+ end
261
+
262
+ omniauth_callback = proc do
263
+ session[:user_data] = {}
264
+
265
+ unless instance_eval(&on_login_proc)
266
+ halt 403, {'Content-Type' => 'text/plain'}, 'Forbidden (on_login_proc policy)'
267
+ end
268
+
269
+ session[:logged_in_at] = Time.now.xmlschema
270
+ update_session! env['omniauth.auth']
271
+ end
272
+ get '/auth/:provider/callback', &omniauth_callback
273
+ post '/auth/:provider/callback', &omniauth_callback
274
+
275
+ get '/callback' do # app side
276
+ app_session = decrypt_session_param(params[:session])
277
+ session.merge!(app_session)
278
+ redirect session.delete(:back_to)
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,3 @@
1
+ module NginxOmniauthAdapter
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "nginx_omniauth_adapter/version"
2
+ require "nginx_omniauth_adapter/app"
3
+
4
+ module NginxOmniauthAdapter
5
+ def self.app(*args)
6
+ App.rack *args
7
+ end
8
+ end
@@ -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 'nginx_omniauth_adapter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nginx_omniauth_adapter"
8
+ spec.version = NginxOmniauthAdapter::VERSION
9
+ spec.authors = ["Shota Fukumori (sora_h)"]
10
+ spec.email = ["her@sorah.jp"]
11
+
12
+ spec.summary = %q{oauth2 adapter for ngx_http_auth_request_module}
13
+ spec.homepage = "https://github.com/sorah/nginx_omniauth_adapter"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "sinatra"
22
+ spec.add_dependency "omniauth"
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "rack-test"
28
+ spec.add_development_dependency "mechanize"
29
+ end
data/script/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nginx_omniauth_adapter"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/script/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nginx_omniauth_adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shota Fukumori (sora_h)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: omniauth
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
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: mechanize
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:
112
+ email:
113
+ - her@sorah.jp
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Dockerfile
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - config.ru
127
+ - example/Procfile
128
+ - example/nginx-site.conf
129
+ - example/nginx.conf
130
+ - example/test_backend.rb
131
+ - lib/nginx_omniauth_adapter.rb
132
+ - lib/nginx_omniauth_adapter/app.rb
133
+ - lib/nginx_omniauth_adapter/version.rb
134
+ - nginx_omniauth_adapter.gemspec
135
+ - script/console
136
+ - script/setup
137
+ homepage: https://github.com/sorah/nginx_omniauth_adapter
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.5.0
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: oauth2 adapter for ngx_http_auth_request_module
161
+ test_files: []