keycard 0.1.0
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 +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
|
+
[](https://travis-ci.org/mlibrary/keycard?branch=master)
|
2
|
+
[](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: []
|