keycard 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/LICENSE.md +27 -0
- data/README.md +46 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/db/migrations/1_create_tables.rb +41 -0
- data/keycard.gemspec +41 -0
- data/lib/keycard.rb +13 -0
- data/lib/keycard/db.rb +159 -0
- data/lib/keycard/institution_finder.rb +62 -0
- data/lib/keycard/railtie.rb +110 -0
- data/lib/keycard/request_attributes.rb +26 -0
- data/lib/keycard/version.rb +5 -0
- data/lib/tasks/migrate.rake +75 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f5d7f477660b581edd56242cba917ceac9a92c56
|
4
|
+
data.tar.gz: 64257be7f76d1497ca3f8397e18e6c56d7e49269
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6105dc8009f59f487d8f463d97325ef021f2f922ada9d912cdf6b83d193efe1c9d7cbf86af1c9c0fb3c3959b50dda8ef73dcb55162e61e7b41a091f08f1ff0e7
|
7
|
+
data.tar.gz: c55623373226fffa991ac9ef370a71085ca2da258080ab3ea6046788ebc9f691eaac5bd1bed5c2029ab6ea8131f608981ef9a9e8c781ca20a643824bf260e73d
|
data/.envrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
PATH_add bin
|
data/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/_yardoc/
|
4
|
+
/coverage/
|
5
|
+
/doc/
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/tmp/
|
9
|
+
|
10
|
+
Gemfile.lock
|
11
|
+
|
12
|
+
# rspec failure tracking
|
13
|
+
.rspec_status
|
14
|
+
|
15
|
+
# Keycard database files
|
16
|
+
/db/*.sqlite
|
17
|
+
/db/keycard.log
|
18
|
+
/db/keycard.yml
|
19
|
+
|
20
|
+
keycard*.gem
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Rails:
|
2
|
+
Enabled: true
|
3
|
+
|
4
|
+
Rails/Delegate:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
# inherit_gem:
|
8
|
+
# rubocop-rails:
|
9
|
+
# - config/rails.yml
|
10
|
+
|
11
|
+
AllCops:
|
12
|
+
DisplayCopNames: true
|
13
|
+
TargetRubyVersion: 2.4
|
14
|
+
Exclude:
|
15
|
+
- 'bin/**/*'
|
16
|
+
- 'vendor/**/*'
|
17
|
+
|
18
|
+
Layout/MultilineMethodDefinitionBraceLayout:
|
19
|
+
EnforcedStyle: same_line
|
20
|
+
|
21
|
+
Metrics/LineLength:
|
22
|
+
Max: 110
|
23
|
+
|
24
|
+
Metrics/BlockLength:
|
25
|
+
Exclude:
|
26
|
+
- '*.gemspec'
|
27
|
+
ExcludedMethods: ['describe', 'context']
|
28
|
+
|
29
|
+
Style/StringLiterals:
|
30
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2018, The Regents of the University of Michigan.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are
|
6
|
+
met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
* Neither the name of the The University of Michigan nor the
|
14
|
+
names of its contributors may be used to endorse or promote products
|
15
|
+
derived from this software without specific prior written permission.
|
16
|
+
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS OF THE UNIVERSITY OF MICHIGAN AND
|
18
|
+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
19
|
+
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE
|
21
|
+
UNIVERSITY OF MICHIGAN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
23
|
+
TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
24
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
25
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
26
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/mlibrary/keycard.svg?branch=master)](https://travis-ci.org/mlibrary/keycard?branch=master)
|
2
|
+
[![Coverage Status](https://coveralls.io/repos/github/mlibrary/keycard/badge.svg?branch=master)](https://coveralls.io/github/mlibrary/keycard?branch=master)
|
3
|
+
|
4
|
+
# Keycard
|
5
|
+
|
6
|
+
Keycard provides authentication support and user/request information, especially
|
7
|
+
in Rails applications.
|
8
|
+
|
9
|
+
Keycard is designed to give you sound guidelines and integration between
|
10
|
+
authentication and authorization without constraining your application. It
|
11
|
+
takes inspiration from [Sorcery](https://github.com/Sorcery/sorcery), but has
|
12
|
+
four important distinctions:
|
13
|
+
|
14
|
+
1. It does not use mixins to configure a "model that can log in".
|
15
|
+
2. It provides a way to retrieve user and session attributes like directory
|
16
|
+
information or IP address-based region, rather than being strictly about
|
17
|
+
logging in and out.
|
18
|
+
3. It only provides one built-in strategy for logins, focused on single sign-on
|
19
|
+
scenarios.
|
20
|
+
4. It offers an optional group implementation for whatever objects your
|
21
|
+
application manages as accounts or users.
|
22
|
+
|
23
|
+
The ultimate goal is to provide useful tools that integrate easily and simplify
|
24
|
+
building applications that have clean, well-factored designs. Keycard should
|
25
|
+
help you focus on solving your application problems, while remaining invisible
|
26
|
+
-- not magical -- to most of your application.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem 'keycard'
|
34
|
+
```
|
35
|
+
|
36
|
+
And then execute:
|
37
|
+
|
38
|
+
$ bundle
|
39
|
+
|
40
|
+
Or install it yourself as:
|
41
|
+
|
42
|
+
$ gem install keycard
|
43
|
+
|
44
|
+
## License
|
45
|
+
|
46
|
+
Keycard is licensed under the BSD-3-Clause license. See [LICENSE.md](LICENSE.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "keycard"
|
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(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
|
5
|
+
Sequel.migration do
|
6
|
+
change do
|
7
|
+
create_table(:aa_inst) do
|
8
|
+
Integer :uniqueIdentifier, null: false
|
9
|
+
String :organizationName, size: 128, null: false
|
10
|
+
Integer :manager
|
11
|
+
DateTime :lastModifiedTime, default: Sequel::CURRENT_TIMESTAMP, null: false
|
12
|
+
String :lastModifiedBy, size: 64, null: false
|
13
|
+
String :dlpsDeleted, size: 1, fixed: true, null: false
|
14
|
+
|
15
|
+
primary_key [:uniqueIdentifier]
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table(:aa_network, ignore_index_errors: true) do
|
19
|
+
Integer :uniqueIdentifier, null: false
|
20
|
+
String :dlpsDNSName, size: 128
|
21
|
+
String :dlpsCIDRAddress, size: 18
|
22
|
+
Bignum :dlpsAddressStart
|
23
|
+
Bignum :dlpsAddressEnd
|
24
|
+
String :dlpsAccessSwitch, size: 5, null: false
|
25
|
+
String :coll, size: 32
|
26
|
+
Integer :inst
|
27
|
+
DateTime :lastModifiedTime, default: Sequel::CURRENT_TIMESTAMP, null: false
|
28
|
+
String :lastModifiedBy, size: 64, null: false
|
29
|
+
String :dlpsDeleted, size: 1, fixed: true, null: false
|
30
|
+
|
31
|
+
check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:dlpsAddressStart), 0)
|
32
|
+
check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:dlpsAddressEnd), 0)
|
33
|
+
primary_key [:uniqueIdentifier]
|
34
|
+
|
35
|
+
index [:dlpsAddressEnd], name: :network_dlpsAddressEnd_index
|
36
|
+
index [:dlpsAddressStart], name: :network_dlpsAddressStart_index
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:enable Metrics/BlockLength
|
data/keycard.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib = File.expand_path("../lib", __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require "keycard/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = "keycard"
|
10
|
+
spec.version = Keycard::VERSION
|
11
|
+
spec.authors = ["Noah Botimer", "Aaron Elkiss"]
|
12
|
+
spec.email = ["botimer@umich.edu", "aelkiss@umich.edu"]
|
13
|
+
spec.license = "BSD-3-Clause"
|
14
|
+
|
15
|
+
spec.summary = <<~SUMMARY
|
16
|
+
Keycard provides authentication support and user/request information,
|
17
|
+
especially in Rails applications.
|
18
|
+
SUMMARY
|
19
|
+
spec.homepage = "https://github.com/mlibrary/keycard"
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
f.match(%r{^(test|spec|features)/})
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "mysql2"
|
29
|
+
spec.add_dependency "sequel"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
32
|
+
spec.add_development_dependency "coveralls", "~> 0.8"
|
33
|
+
spec.add_development_dependency "pry"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
36
|
+
spec.add_development_dependency "rubocop", "~> 0.52"
|
37
|
+
spec.add_development_dependency "rubocop-rails", "~> 1.1"
|
38
|
+
spec.add_development_dependency "rubocop-rspec", "~> 1.16"
|
39
|
+
spec.add_development_dependency "sqlite3"
|
40
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
41
|
+
end
|
data/lib/keycard.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "keycard/version"
|
4
|
+
require "sequel"
|
5
|
+
|
6
|
+
# All of the Keycard components are contained within this top-level module.
|
7
|
+
module Keycard
|
8
|
+
end
|
9
|
+
|
10
|
+
require "keycard/db"
|
11
|
+
require "keycard/railtie" if defined?(Rails)
|
12
|
+
require "keycard/request_attributes"
|
13
|
+
require "keycard/institution_finder"
|
data/lib/keycard/db.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'logger'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Keycard
|
8
|
+
# Module for database interactions for Keycard.
|
9
|
+
module DB
|
10
|
+
# Any error with the database that Keycard itself detects but cannot handle.
|
11
|
+
class DatabaseError < StandardError; end
|
12
|
+
|
13
|
+
CONNECTION_ERROR = 'The Keycard database is not initialized. Call initialize! first.'
|
14
|
+
|
15
|
+
ALREADY_CONNECTED = 'Already connected; refusing to connect to another database.'
|
16
|
+
|
17
|
+
MISSING_CONFIG = <<~MSG
|
18
|
+
KEYCARD_DATABASE_URL and DATABASE_URL are both missing and a connection
|
19
|
+
has not been configured. Cannot connect to the Keycard database.
|
20
|
+
See Keycard::DB.connect! for help.
|
21
|
+
MSG
|
22
|
+
|
23
|
+
LOAD_ERROR = <<~MSG
|
24
|
+
Error loading Keycard database models.
|
25
|
+
Verify connection information and that the database is migrated.
|
26
|
+
MSG
|
27
|
+
|
28
|
+
SCHEMA_HEADER = "# Keycard Database Version\n"
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# Initialize Keycard
|
32
|
+
#
|
33
|
+
# This connects to the database if it has not already happened and
|
34
|
+
# requires all of the Keycard model classes. It is required to do the
|
35
|
+
# connection setup first because of the design decision in Sequel that
|
36
|
+
# the schema is examined at the time of extending Sequel::Model.
|
37
|
+
def initialize!
|
38
|
+
connect! unless connected?
|
39
|
+
begin
|
40
|
+
model_files.each do |file|
|
41
|
+
require_relative file
|
42
|
+
end
|
43
|
+
rescue Sequel::DatabaseError, NoMethodError => e
|
44
|
+
raise DatabaseError, LOAD_ERROR + "\n" + e.message
|
45
|
+
end
|
46
|
+
db
|
47
|
+
end
|
48
|
+
|
49
|
+
# Connect to the Keycard database.
|
50
|
+
#
|
51
|
+
# The default is to use the settings under {.config}, but can be
|
52
|
+
# supplied here (and they will be merged into config as a side effect).
|
53
|
+
# The keys that will be used from either source are documented here as
|
54
|
+
# the options.
|
55
|
+
#
|
56
|
+
# Only one "mode" will be used; the first of these supplied will take
|
57
|
+
# precedence:
|
58
|
+
#
|
59
|
+
# 1. An already-connected {Sequel::Database} object
|
60
|
+
# 2. A connection string
|
61
|
+
# 3. A connection options hash
|
62
|
+
#
|
63
|
+
# While Keycard serves as a singleton, this will raise a DatabaseError
|
64
|
+
# if already connected. Check `connected?` if you are unsure.
|
65
|
+
#
|
66
|
+
# @see {Sequel.connect}
|
67
|
+
# @param [Hash] config Optional connection config
|
68
|
+
# @option config [String] :url A Sequel database URL
|
69
|
+
# @option config [Hash] :opts A set of connection options
|
70
|
+
# @option config [Sequel::Database] :db An already-connected database;
|
71
|
+
# @return [Sequel::Database] The initialized database connection
|
72
|
+
def connect!(config = {})
|
73
|
+
raise DatabaseError, ALREADY_CONNECTED if connected?
|
74
|
+
merge_config!(config)
|
75
|
+
raise DatabaseError, MISSING_CONFIG if self.config.db.nil? && conn_opts.empty?
|
76
|
+
|
77
|
+
# We splat here because we might give one or two arguments depending
|
78
|
+
# on whether we have a string or not; to add our logger regardless.
|
79
|
+
@db = self.config.db || Sequel.connect(*conn_opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Run any pending migrations.
|
83
|
+
# This will connect with the current config if not already conencted.
|
84
|
+
def migrate!
|
85
|
+
connect! unless connected?
|
86
|
+
Sequel.extension :migration
|
87
|
+
Sequel::Migrator.run(db, File.join(__dir__, '../../db/migrations'), table: schema_table)
|
88
|
+
end
|
89
|
+
|
90
|
+
def schema_table
|
91
|
+
:keycard_schema
|
92
|
+
end
|
93
|
+
|
94
|
+
def schema_file
|
95
|
+
'db/keycard.yml'
|
96
|
+
end
|
97
|
+
|
98
|
+
def dump_schema!
|
99
|
+
connect! unless connected?
|
100
|
+
version = db[schema_table].first.to_yaml
|
101
|
+
File.write(schema_file, SCHEMA_HEADER + version)
|
102
|
+
end
|
103
|
+
|
104
|
+
def load_schema!
|
105
|
+
connect! unless connected?
|
106
|
+
version = YAML.load_file(schema_file)[:version]
|
107
|
+
db[schema_table].delete
|
108
|
+
db[schema_table].insert(version: version)
|
109
|
+
end
|
110
|
+
|
111
|
+
def model_files
|
112
|
+
[]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Merge url, opts, or db settings from a hash into our config
|
116
|
+
def merge_config!(config = {})
|
117
|
+
self.config.url = config[:url] if config.key?(:url)
|
118
|
+
self.config.opts = config[:opts] if config.key?(:opts)
|
119
|
+
self.config.db = config[:db] if config.key?(:db)
|
120
|
+
end
|
121
|
+
|
122
|
+
def conn_opts
|
123
|
+
log = { logger: Logger.new('db/keycard.log') }
|
124
|
+
url = config.url
|
125
|
+
opts = config.opts
|
126
|
+
if url
|
127
|
+
[url, log]
|
128
|
+
elsif opts
|
129
|
+
[log.merge(opts)]
|
130
|
+
else
|
131
|
+
[]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def config
|
136
|
+
@config ||= OpenStruct.new(
|
137
|
+
url: ENV['KEYCARD_DATABASE_URL'] || ENV['DATABASE_URL']
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def connected?
|
142
|
+
!@db.nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
# The Keycard database
|
146
|
+
# @return [Sequel::Database] The connected database; be sure to call initialize! first.
|
147
|
+
def db
|
148
|
+
raise DatabaseError, CONNECTION_ERROR unless connected?
|
149
|
+
@db
|
150
|
+
end
|
151
|
+
|
152
|
+
# Forward the Sequel::Database []-syntax down to db for convenience.
|
153
|
+
# Everything else must be called on db directly, but this is nice sugar.
|
154
|
+
def [](*args)
|
155
|
+
db[*args]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Keycard
|
6
|
+
# looks up institution ID(s) by IP address
|
7
|
+
class InstitutionFinder
|
8
|
+
INST_QUERY = <<~SQL
|
9
|
+
SELECT inst FROM aa_network WHERE
|
10
|
+
? >= dlpsAddressStart
|
11
|
+
AND ? <= dlpsAddressEnd
|
12
|
+
AND dlpsAccessSwitch = 'allow'
|
13
|
+
AND dlpsDeleted = 'f'
|
14
|
+
AND inst is not null
|
15
|
+
AND inst NOT IN
|
16
|
+
( SELECT inst FROM aa_network WHERE
|
17
|
+
? >= dlpsAddressStart
|
18
|
+
AND ? <= dlpsAddressEnd
|
19
|
+
AND dlpsAccessSwitch = 'deny'
|
20
|
+
AND dlpsDeleted = 'f' )
|
21
|
+
SQL
|
22
|
+
|
23
|
+
def initialize(db: Keycard::DB.db)
|
24
|
+
@db = db
|
25
|
+
@stmt = @db[INST_QUERY, *[:$client_ip] * 4].prepare(:select, :unused)
|
26
|
+
end
|
27
|
+
|
28
|
+
def attributes_for(request)
|
29
|
+
return {} unless (numeric_ip = numeric_ip(client_ip(request)))
|
30
|
+
|
31
|
+
insts = insts_for_ip(numeric_ip)
|
32
|
+
|
33
|
+
if !insts.empty?
|
34
|
+
{ 'dlpsInstitutionId' => insts }
|
35
|
+
else
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :stmt
|
43
|
+
|
44
|
+
def insts_for_ip(numeric_ip)
|
45
|
+
stmt.call(client_ip: numeric_ip).map { |row| row[:inst] }
|
46
|
+
end
|
47
|
+
|
48
|
+
def numeric_ip(dotted_ip)
|
49
|
+
return unless dotted_ip
|
50
|
+
|
51
|
+
begin
|
52
|
+
IPAddr.new(dotted_ip).to_i
|
53
|
+
rescue IPAddr::InvalidAddressError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def client_ip(request)
|
59
|
+
request.get_header('X-Forwarded-For')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Keycard
|
4
|
+
# Railtie to hook Keycard into Rails applications.
|
5
|
+
#
|
6
|
+
# This does three things at present:
|
7
|
+
#
|
8
|
+
# 1. Loads our rake tasks, so you can run keycard:migrate from the app.
|
9
|
+
# 2. Pulls the Rails database information off of the ActiveRecord
|
10
|
+
# connection and puts it on Keycard::DB.config before any application
|
11
|
+
# initializers are run.
|
12
|
+
# 3. Sets up the Keycard database connection after application
|
13
|
+
# initializers have run, if it has not already been done and we are not
|
14
|
+
# running as a Rake task. This condition is key because when we are in
|
15
|
+
# rails server or console, we want to initialize!, but when we are in
|
16
|
+
# a rake task to update the database, we have to let it connect, but
|
17
|
+
# not initialize.
|
18
|
+
class Railtie < Rails::Railtie
|
19
|
+
railtie_name :keycard
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Register a callback to run before anything in 'config/initializers' runs.
|
23
|
+
# The block will get a reference to Keycard::DB.config as its only parameter.
|
24
|
+
def before_initializers(&block)
|
25
|
+
before_blocks << block
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register a callback to run after anything in 'config/initializers' runs.
|
29
|
+
# The block will get a reference to Keycard::DB.config as its only parameter.
|
30
|
+
# Keycard::DB.initialize! will not have been automatically called at this
|
31
|
+
# point, so this is an opportunity to do so if an initializer has not.
|
32
|
+
def after_initializers(&block)
|
33
|
+
after_blocks << block
|
34
|
+
end
|
35
|
+
|
36
|
+
# Register a callback to run when Keycard is ready and fully initialized.
|
37
|
+
# This will happen once in production, and on each request in development.
|
38
|
+
# If you need to do something once in development, you can choose between
|
39
|
+
# keeping a flag or using the after_initializers.
|
40
|
+
def when_keycard_is_ready(&block)
|
41
|
+
ready_blocks << block
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_blocks
|
45
|
+
@before ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_blocks
|
49
|
+
@after ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def ready_blocks
|
53
|
+
@ready ||= []
|
54
|
+
end
|
55
|
+
|
56
|
+
def under_rake!
|
57
|
+
@rake = true
|
58
|
+
end
|
59
|
+
|
60
|
+
def under_rake?
|
61
|
+
@rake ||= false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# This runs before anything in 'config/initializers' runs.
|
66
|
+
initializer "keycard.before_initializers", before: :load_config_initializers do
|
67
|
+
config = Keycard::DB.config
|
68
|
+
unless config.url
|
69
|
+
opts = ActiveRecord::Base.connection.instance_variable_get(:@config).dup
|
70
|
+
opts.delete(:flags)
|
71
|
+
config[:opts] = opts
|
72
|
+
end
|
73
|
+
|
74
|
+
Railtie.before_blocks.each do |block|
|
75
|
+
block.call(config.to_h)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# This runs after everything in 'config/initializers' runs.
|
80
|
+
initializer "keycard.after_initializers", after: :load_config_initializers do
|
81
|
+
config = Keycard::DB.config
|
82
|
+
Railtie.after_blocks.each do |block|
|
83
|
+
block.call(config.to_h)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# This runs before any block registered under a `config.to_prepare`, which
|
88
|
+
# could be in plugins or initializers that want to use a fully configured
|
89
|
+
# Keycard instance. The `to_prepare` hook is run once at the start of a
|
90
|
+
# production instance and for every request in development (unless caching
|
91
|
+
# is turned on so there is no reloading).
|
92
|
+
initializer "keycard.ready", after: :finisher_hook do
|
93
|
+
Keycard::DB.initialize! unless Railtie.under_rake?
|
94
|
+
|
95
|
+
Railtie.ready_blocks.each do |block|
|
96
|
+
block.call(Keycard::DB.db)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def rake_files
|
101
|
+
base = Pathname(__dir__) + '../tasks/'
|
102
|
+
[base + 'migrate.rake']
|
103
|
+
end
|
104
|
+
|
105
|
+
rake_tasks do
|
106
|
+
Railtie.under_rake!
|
107
|
+
rake_files.each { |file| load file }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Keycard
|
4
|
+
# This class is responsible for extracting the user attributes (i.e. the
|
5
|
+
# complete set of things that determine the user's #identity), given a Rack
|
6
|
+
# request.
|
7
|
+
class RequestAttributes
|
8
|
+
def initialize(request, finder: InstitutionFinder.new)
|
9
|
+
@finder = finder
|
10
|
+
@request = request
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](attr)
|
14
|
+
all[attr]
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
finder.attributes_for(request)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :finder
|
24
|
+
attr_reader :request
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'keycard'
|
5
|
+
|
6
|
+
if defined?(Rails)
|
7
|
+
# When db:schema:dump is called directly, we can tack this on.
|
8
|
+
# If we do it unconditionally, db:migrate will try to dump before we have
|
9
|
+
# been able to migrate the Keycard tables.
|
10
|
+
if Rake.application.top_level_tasks.include?('db:schema:dump')
|
11
|
+
Rake::Task['db:schema:dump'].enhance do
|
12
|
+
Rake::Task['keycard:schema:dump'].invoke
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Run our schema load to make sure that the version number is stored in
|
17
|
+
# schema_info, so migrations don't try to double-run. The actual table
|
18
|
+
# structure is handled by the Rails schema:dump and schema:load.
|
19
|
+
# A db:setup will trigger this, so we don't have to handle it separately.
|
20
|
+
Rake::Task['db:schema:load'].enhance do
|
21
|
+
Rake::Task['keycard:schema:load'].invoke
|
22
|
+
end
|
23
|
+
|
24
|
+
# We hook into db:migrate for convenience.
|
25
|
+
Rake::Task['db:migrate'].enhance do
|
26
|
+
Rake::Task['keycard:migrate'].invoke
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
namespace :keycard do
|
32
|
+
desc "Migrate the Keycard database to the latest version"
|
33
|
+
task :migrate do
|
34
|
+
if defined?(Rails)
|
35
|
+
# Load the 'environment', which does the full Rails initialization.
|
36
|
+
# The Railtie is smart enough to know whether we are in a Rake task,
|
37
|
+
# so it can avoid initializing and we can migrate safely before the
|
38
|
+
# models are loaded.
|
39
|
+
Rake::Task['environment'].invoke
|
40
|
+
end
|
41
|
+
|
42
|
+
# After migrating, we initialize here, even though it isn't strictly
|
43
|
+
# necessary, but it will ensure that migration does a small sanity check
|
44
|
+
# that at least all of the tables expected by model classes exist.
|
45
|
+
Keycard::DB.migrate!
|
46
|
+
Keycard::DB.initialize!
|
47
|
+
end
|
48
|
+
|
49
|
+
# We don't bother defining the schema:dump and schema:load tasks if we're
|
50
|
+
# not running under Rails. They exist only to cooperate with the dumps done
|
51
|
+
# by Rails, since schema.rb includes any Keycard tables in the same
|
52
|
+
# database as the application -- a convenient default mode.
|
53
|
+
if defined?(Rails)
|
54
|
+
namespace :schema do
|
55
|
+
desc "Dump the Keycard version to db/keycard.yml"
|
56
|
+
task :dump do
|
57
|
+
Rake::Task['environment'].invoke
|
58
|
+
Keycard::DB.dump_schema!
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Load the Keycard version from db/keycard.yml"
|
62
|
+
task :load do
|
63
|
+
Rake::Task['environment'].invoke
|
64
|
+
Keycard::DB.load_schema!
|
65
|
+
end
|
66
|
+
|
67
|
+
# When running under Rails, we dump the schema after migrating so
|
68
|
+
# everything stays synced up for db:setup against a new database.
|
69
|
+
# Rake::Task['keycard:schema:dump'].invoke
|
70
|
+
Rake::Task['keycard:migrate'].enhance do
|
71
|
+
Rake::Task['keycard:schema:dump'].invoke
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keycard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Noah Botimer
|
8
|
+
- Aaron Elkiss
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-03-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mysql2
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: sequel
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.16'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.16'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: coveralls
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.8'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.8'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: pry
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rake
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '10.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '10.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rspec
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '3.0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '3.0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: rubocop
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0.52'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0.52'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rubocop-rails
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - "~>"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '1.1'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '1.1'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: rubocop-rspec
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - "~>"
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '1.16'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - "~>"
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '1.16'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: sqlite3
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: yard
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - "~>"
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0.9'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - "~>"
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0.9'
|
182
|
+
description:
|
183
|
+
email:
|
184
|
+
- botimer@umich.edu
|
185
|
+
- aelkiss@umich.edu
|
186
|
+
executables: []
|
187
|
+
extensions: []
|
188
|
+
extra_rdoc_files: []
|
189
|
+
files:
|
190
|
+
- ".envrc"
|
191
|
+
- ".gitignore"
|
192
|
+
- ".rspec"
|
193
|
+
- ".rubocop.yml"
|
194
|
+
- ".travis.yml"
|
195
|
+
- Gemfile
|
196
|
+
- LICENSE.md
|
197
|
+
- README.md
|
198
|
+
- Rakefile
|
199
|
+
- bin/console
|
200
|
+
- bin/setup
|
201
|
+
- db/migrations/1_create_tables.rb
|
202
|
+
- keycard.gemspec
|
203
|
+
- lib/keycard.rb
|
204
|
+
- lib/keycard/db.rb
|
205
|
+
- lib/keycard/institution_finder.rb
|
206
|
+
- lib/keycard/railtie.rb
|
207
|
+
- lib/keycard/request_attributes.rb
|
208
|
+
- lib/keycard/version.rb
|
209
|
+
- lib/tasks/migrate.rake
|
210
|
+
homepage: https://github.com/mlibrary/keycard
|
211
|
+
licenses:
|
212
|
+
- BSD-3-Clause
|
213
|
+
metadata: {}
|
214
|
+
post_install_message:
|
215
|
+
rdoc_options: []
|
216
|
+
require_paths:
|
217
|
+
- lib
|
218
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
version: '0'
|
228
|
+
requirements: []
|
229
|
+
rubyforge_project:
|
230
|
+
rubygems_version: 2.6.13
|
231
|
+
signing_key:
|
232
|
+
specification_version: 4
|
233
|
+
summary: Keycard provides authentication support and user/request information, especially
|
234
|
+
in Rails applications.
|
235
|
+
test_files: []
|