keycard 0.3.4 → 0.4.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 +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/test.yml +32 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +9 -0
- data/README.md +18 -4
- data/Rakefile +1 -1
- data/bin/rspec +29 -0
- data/bin/standardrb +29 -0
- data/db/migrations/1_create_tables.rb +0 -2
- data/keycard.gemspec +13 -9
- data/lib/keycard/controller_methods.rb +6 -6
- data/lib/keycard/db.rb +11 -11
- data/lib/keycard/digest_key.rb +7 -13
- data/lib/keycard/institution_finder.rb +2 -2
- data/lib/keycard/notary.rb +1 -1
- data/lib/keycard/railtie.rb +4 -4
- data/lib/keycard/request/attributes.rb +6 -6
- data/lib/keycard/request/attributes_factory.rb +3 -3
- data/lib/keycard/request/cosign_attributes.rb +2 -2
- data/lib/keycard/request/direct_attributes.rb +2 -2
- data/lib/keycard/request/proxied_attributes.rb +2 -2
- data/lib/keycard/request/shibboleth_attributes.rb +17 -17
- data/lib/keycard/request.rb +6 -6
- data/lib/keycard/token.rb +4 -4
- data/lib/keycard/version.rb +1 -1
- data/lib/keycard.rb +1 -0
- data/lib/tasks/migrate.rake +14 -14
- metadata +46 -19
- data/.rubocop.yml +0 -36
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 977ad57d8c2f90bff10d401257afa4425e2024f9e5f9495070ffd69177c6ed64
|
|
4
|
+
data.tar.gz: ff159cd688e1ae9a10f971e14380a844dd7bf95c81a2d8911cdb0e851a251d61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f561da3e734d6bde76d74562f01ea66f270fea189f589f6f8849dda472860d33e2f0efabee88e752ea1ac0c50b2c724825f2a630e5a90c4d1d2e015f7db2deb
|
|
7
|
+
data.tar.gz: a4067ad2e10a4573effc4e42148889ddf8e3f380c748c43945732ae64a5409ba4cb9053d4ba68413e11f995f2312019509c5ec12d2451ca87af3fca0fb77d0a6
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ ubuntu-latest ]
|
|
14
|
+
ruby-version: [3.2, 3.3, 3.4, 4.0]
|
|
15
|
+
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
21
|
+
uses: ruby/setup-ruby@v1
|
|
22
|
+
with:
|
|
23
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
- name: Run linter for Ruby ${{ matrix.ruby-version }} on ${{ matrix.os }}
|
|
26
|
+
run: bin/standardrb
|
|
27
|
+
- name: Run tests for Ruby ${{ matrix.ruby-version }} on ${{ matrix.os }}
|
|
28
|
+
run: bin/rspec
|
|
29
|
+
- name: Report to Coveralls
|
|
30
|
+
uses: coverallsapp/github-action@v2.1.0
|
|
31
|
+
with:
|
|
32
|
+
github-token: ${{ secrets.github_token }}
|
data/.standard.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby_version: 3.0.0
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[](https://github.com/mlibrary/keycard/actions/workflows/test.yml)
|
|
2
|
+
[](https://coveralls.io/github/mlibrary/keycard?branch=main)
|
|
3
3
|
[](https://keycard.readthedocs.io/en/latest)
|
|
4
4
|
[](https://www.rubydoc.info/gems/keycard)
|
|
5
5
|
|
|
@@ -47,7 +47,7 @@ associated with institutions), and the access mode (whether your application is
|
|
|
47
47
|
served directly or behind a reverse proxy). These will be unified eventually,
|
|
48
48
|
but for now, they are configured separately.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
### For the Database
|
|
51
51
|
|
|
52
52
|
For the database, there is a Railtie that, when running in a Rails app,
|
|
53
53
|
attempts to use the same connection information as ActiveRecord. If you are
|
|
@@ -66,7 +66,7 @@ to pass to the Sequel connection or set `Keycard::DB.config.url` to use a
|
|
|
66
66
|
connction string. The latter is equivalent to setting the `KEYCARD_DATABASE_URL`
|
|
67
67
|
environment variable.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
### For the Access Mode
|
|
70
70
|
|
|
71
71
|
To extract the username and client IP from each request, Keycard must be
|
|
72
72
|
configured for an "access mode". This can be set in an initializer, under the
|
|
@@ -78,6 +78,20 @@ Under the hood, these modes amount to using either `REMOTE_USER` and
|
|
|
78
78
|
`REMOTE_ADDR` in the environment set by the Ruby webserver for direct mode or
|
|
79
79
|
the `X-Forwarded-User` and `X-Forwarded-For` headers set by a reverse proxy.
|
|
80
80
|
|
|
81
|
+
## Compatibility
|
|
82
|
+
|
|
83
|
+
Keycard is intended to be compatible with all community-supported Ruby branches (i.e., minor versions), currently:
|
|
84
|
+
|
|
85
|
+
- 3.2
|
|
86
|
+
- 3.3
|
|
87
|
+
- 3.4
|
|
88
|
+
- 4.0
|
|
89
|
+
|
|
90
|
+
We prefer the newest syntax and linting rules that preserve compatibility with the oldest branch in normal maintenance.
|
|
91
|
+
When the security maintenance for a branch expires, Keycard's compatibility should be considered unsupported.
|
|
92
|
+
|
|
93
|
+
See also, [Ruby's branch maintenance policy](https://www.ruby-lang.org/en/downloads/branches/).
|
|
94
|
+
|
|
81
95
|
## License
|
|
82
96
|
|
|
83
97
|
Keycard is licensed under the BSD-3-Clause license. See [LICENSE.md](LICENSE.md).
|
data/Rakefile
CHANGED
data/bin/rspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/standardrb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'standardrb' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("standard", "standardrb")
|
data/keycard.gemspec
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
lib = File.expand_path(
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require "keycard/version"
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name
|
|
8
|
+
spec.name = "keycard"
|
|
9
9
|
spec.version = Keycard::VERSION
|
|
10
10
|
spec.authors = ["Noah Botimer", "Aaron Elkiss"]
|
|
11
|
-
spec.email
|
|
11
|
+
spec.email = ["botimer@umich.edu", "aelkiss@umich.edu"]
|
|
12
12
|
spec.license = "BSD-3-Clause"
|
|
13
13
|
|
|
14
14
|
spec.summary = <<~SUMMARY
|
|
@@ -20,19 +20,23 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
21
21
|
f.match(%r{^(test|spec|features)/})
|
|
22
22
|
end
|
|
23
|
-
spec.bindir
|
|
24
|
-
spec.executables
|
|
23
|
+
spec.bindir = "exe"
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ["lib"]
|
|
26
26
|
|
|
27
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
28
|
+
|
|
27
29
|
spec.add_dependency "sequel"
|
|
28
30
|
|
|
29
31
|
spec.add_development_dependency "bundler"
|
|
30
|
-
spec.add_development_dependency "
|
|
32
|
+
spec.add_development_dependency "logger"
|
|
33
|
+
spec.add_development_dependency "simplecov"
|
|
34
|
+
spec.add_development_dependency "simplecov-lcov"
|
|
35
|
+
spec.add_development_dependency "ostruct"
|
|
31
36
|
spec.add_development_dependency "pry"
|
|
32
37
|
spec.add_development_dependency "rake"
|
|
33
38
|
spec.add_development_dependency "rspec"
|
|
34
|
-
spec.add_development_dependency "
|
|
35
|
-
spec.add_development_dependency "
|
|
36
|
-
spec.add_development_dependency "sqlite3", "~> 1.3.13"
|
|
39
|
+
spec.add_development_dependency "standard", "~> 1.53"
|
|
40
|
+
spec.add_development_dependency "sqlite3", "~> 2.9.0"
|
|
37
41
|
spec.add_development_dependency "yard"
|
|
38
42
|
end
|
|
@@ -34,10 +34,10 @@ module Keycard
|
|
|
34
34
|
def validate_session
|
|
35
35
|
csrf_token = session[:_csrf_token]
|
|
36
36
|
elapsed = begin
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
Time.now - Time.at(session[:timestamp] || 0)
|
|
38
|
+
rescue
|
|
39
|
+
session_timeout
|
|
40
|
+
end
|
|
41
41
|
reset_session if elapsed >= session_timeout
|
|
42
42
|
session[:_csrf_token] = csrf_token
|
|
43
43
|
session[:timestamp] = Time.now.to_i if session.key?(:timestamp)
|
|
@@ -64,7 +64,7 @@ module Keycard
|
|
|
64
64
|
# passed to each authentication method
|
|
65
65
|
# @return [Boolean] whether the login attempt was successful
|
|
66
66
|
def login(**credentials)
|
|
67
|
-
authentication(credentials).authenticated?.tap do |success|
|
|
67
|
+
authentication(**credentials).authenticated?.tap do |success|
|
|
68
68
|
setup_session if success
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -88,7 +88,7 @@ module Keycard
|
|
|
88
88
|
|
|
89
89
|
def authentication(**credentials)
|
|
90
90
|
request.env["keycard.authentication"] ||=
|
|
91
|
-
notary.authenticate(request, session, credentials)
|
|
91
|
+
notary.authenticate(request, session, **credentials)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# The session timeout, in seconds. Sessions will be cleared before any
|
data/lib/keycard/db.rb
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "ostruct"
|
|
4
|
+
require "logger"
|
|
5
|
+
require "yaml"
|
|
6
6
|
|
|
7
7
|
# Module for database interactions for Keycard.
|
|
8
8
|
module Keycard::DB
|
|
9
9
|
# Any error with the database that Keycard itself detects but cannot handle.
|
|
10
10
|
class DatabaseError < StandardError; end
|
|
11
11
|
|
|
12
|
-
CONNECTION_ERROR =
|
|
12
|
+
CONNECTION_ERROR = "The Keycard database is not initialized. Call initialize! first."
|
|
13
13
|
|
|
14
|
-
ALREADY_CONNECTED =
|
|
14
|
+
ALREADY_CONNECTED = "Already connected; refusing to connect to another database."
|
|
15
15
|
|
|
16
16
|
MISSING_CONFIG = <<~MSG
|
|
17
17
|
KEYCARD_DATABASE_URL and DATABASE_URL are both missing and a connection
|
|
@@ -85,7 +85,7 @@ module Keycard::DB
|
|
|
85
85
|
return if config.readonly
|
|
86
86
|
|
|
87
87
|
Sequel.extension :migration
|
|
88
|
-
Sequel::Migrator.run(db, File.join(__dir__,
|
|
88
|
+
Sequel::Migrator.run(db, File.join(__dir__, "../../db/migrations"), table: schema_table)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def schema_table
|
|
@@ -93,7 +93,7 @@ module Keycard::DB
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def schema_file
|
|
96
|
-
|
|
96
|
+
"db/keycard.yml"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def dump_schema!
|
|
@@ -115,13 +115,13 @@ module Keycard::DB
|
|
|
115
115
|
|
|
116
116
|
# Merge url, opts, or db settings from a hash into our config
|
|
117
117
|
def merge_config!(config = {})
|
|
118
|
-
self.config.url
|
|
118
|
+
self.config.url = config[:url] if config.key?(:url)
|
|
119
119
|
self.config.opts = config[:opts] if config.key?(:opts)
|
|
120
|
-
self.config.db
|
|
120
|
+
self.config.db = config[:db] if config.key?(:db)
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def conn_opts
|
|
124
|
-
log = {
|
|
124
|
+
log = {logger: Logger.new("db/keycard.log")}
|
|
125
125
|
url = config.url
|
|
126
126
|
opts = config.opts
|
|
127
127
|
if url
|
|
@@ -135,7 +135,7 @@ module Keycard::DB
|
|
|
135
135
|
|
|
136
136
|
def config
|
|
137
137
|
@config ||= OpenStruct.new(
|
|
138
|
-
url: ENV[
|
|
138
|
+
url: ENV["KEYCARD_DATABASE_URL"] || ENV["DATABASE_URL"],
|
|
139
139
|
readonly: false
|
|
140
140
|
)
|
|
141
141
|
end
|
data/lib/keycard/digest_key.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "securerandom"
|
|
|
6
6
|
# A typical digest or api key, ready to be encrypted.
|
|
7
7
|
class Keycard::DigestKey
|
|
8
8
|
class HiddenKeyError < StandardError; end
|
|
9
|
-
HIDDEN_KEY = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
|
9
|
+
HIDDEN_KEY = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
|
10
10
|
|
|
11
11
|
# To simply mint a new key, call #new without any parameters.
|
|
12
12
|
# For wrapping existing, deserialized keys, pass the digest to the constructor.
|
|
@@ -33,11 +33,7 @@ class Keycard::DigestKey
|
|
|
33
33
|
# @raise [HiddenKeyError] This exception is raised if the unhashed key is
|
|
34
34
|
# not available.
|
|
35
35
|
def value
|
|
36
|
-
|
|
37
|
-
@key
|
|
38
|
-
else
|
|
39
|
-
raise HiddenKeyError, "Cannot display hashed/hidden keys"
|
|
40
|
-
end
|
|
36
|
+
@key || raise(HiddenKeyError, "Cannot display hashed/hidden keys")
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
# The result of hashing the key
|
|
@@ -48,12 +44,10 @@ class Keycard::DigestKey
|
|
|
48
44
|
|
|
49
45
|
def eql?(other)
|
|
50
46
|
digest == if other.is_a?(self.class)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
other.digest
|
|
48
|
+
else
|
|
49
|
+
other.to_s
|
|
50
|
+
end
|
|
55
51
|
end
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
alias_method :==, :eql?
|
|
58
53
|
end
|
|
59
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "ipaddr"
|
|
4
4
|
|
|
5
5
|
# looks up institution ID(s) by IP address
|
|
6
6
|
class Keycard::InstitutionFinder
|
|
@@ -36,7 +36,7 @@ class Keycard::InstitutionFinder
|
|
|
36
36
|
insts = insts_for_ip(numeric_ip)
|
|
37
37
|
|
|
38
38
|
if !insts.empty?
|
|
39
|
-
{
|
|
39
|
+
{dlpsInstitutionId: insts}
|
|
40
40
|
else
|
|
41
41
|
{}
|
|
42
42
|
end
|
data/lib/keycard/notary.rb
CHANGED
|
@@ -61,7 +61,7 @@ module Keycard
|
|
|
61
61
|
attributes = attributes_factory.for(request)
|
|
62
62
|
Authentication::Result.new.tap do |result|
|
|
63
63
|
methods.find do |factory|
|
|
64
|
-
factory.call(attributes, session, result, credentials).apply
|
|
64
|
+
factory.call(attributes, session, result, **credentials).apply
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
data/lib/keycard/railtie.rb
CHANGED
|
@@ -67,9 +67,9 @@ class Keycard::Railtie < Rails::Railtie
|
|
|
67
67
|
unless config.url
|
|
68
68
|
case Rails.env
|
|
69
69
|
when "development"
|
|
70
|
-
config[:opts] = {
|
|
70
|
+
config[:opts] = {adapter: "sqlite", database: "db/keycard_development.sqlite3"}
|
|
71
71
|
when "test"
|
|
72
|
-
config[:opts] = {
|
|
72
|
+
config[:opts] = {adapter: "sqlite"}
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -94,8 +94,8 @@ class Keycard::Railtie < Rails::Railtie
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def rake_files
|
|
97
|
-
base = Pathname(__dir__) +
|
|
98
|
-
[base +
|
|
97
|
+
base = Pathname(__dir__) + "../tasks/"
|
|
98
|
+
[base + "migrate.rake"]
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
rake_tasks do
|
|
@@ -55,7 +55,7 @@ module Keycard::Request
|
|
|
55
55
|
# The token supplied by the user via auth_param according to RFC 7235. Typically,
|
|
56
56
|
# this is the API token.
|
|
57
57
|
def auth_token
|
|
58
|
-
Keycard::Token.rfc7235(safe(
|
|
58
|
+
Keycard::Token.rfc7235(safe("HTTP_AUTHORIZATION"))
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# The set of base attributes for this request.
|
|
@@ -76,12 +76,12 @@ module Keycard::Request
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def all
|
|
79
|
-
base.merge!(external).delete_if { |_k, v| v.nil? || v ==
|
|
79
|
+
base.merge!(external).delete_if { |_k, v| v.nil? || v == "" }
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def external
|
|
83
83
|
finders
|
|
84
|
-
.map
|
|
84
|
+
.map { |finder| finder.attributes_for(self) }
|
|
85
85
|
.reduce({}) { |hash, attrs| hash.merge!(attrs) }
|
|
86
86
|
end
|
|
87
87
|
|
|
@@ -116,15 +116,15 @@ module Keycard::Request
|
|
|
116
116
|
# missing values to an empty string, convenient if you need to do string
|
|
117
117
|
# operations like splitting the value.
|
|
118
118
|
def safe(key)
|
|
119
|
-
get(key) ||
|
|
119
|
+
get(key) || ""
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Scrub a value to remove whitespace, filter the special '(null)' value,
|
|
123
123
|
# and translate resulting empty strings to nil.
|
|
124
124
|
def scrub(value)
|
|
125
|
-
str = (value ||
|
|
125
|
+
str = (value || "").strip
|
|
126
126
|
case str
|
|
127
|
-
when nil,
|
|
127
|
+
when nil, "(null)", ""
|
|
128
128
|
nil
|
|
129
129
|
else
|
|
130
130
|
str
|
|
@@ -7,9 +7,9 @@ module Keycard::Request
|
|
|
7
7
|
# use .for instead of naming concrete classes when processing requests.
|
|
8
8
|
class AttributesFactory
|
|
9
9
|
MODE_MAP = {
|
|
10
|
-
direct:
|
|
11
|
-
proxy:
|
|
12
|
-
cosign:
|
|
10
|
+
direct: DirectAttributes,
|
|
11
|
+
proxy: ProxiedAttributes,
|
|
12
|
+
cosign: CosignAttributes,
|
|
13
13
|
shibboleth: ShibbolethAttributes
|
|
14
14
|
}.freeze
|
|
15
15
|
|
|
@@ -7,7 +7,7 @@ module Keycard::Request
|
|
|
7
7
|
# attributes extracted.
|
|
8
8
|
class CosignAttributes < Attributes
|
|
9
9
|
def user_pid
|
|
10
|
-
get
|
|
10
|
+
get "HTTP_X_REMOTE_USER"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def user_eid
|
|
@@ -15,7 +15,7 @@ module Keycard::Request
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def client_ip
|
|
18
|
-
safe(
|
|
18
|
+
safe("HTTP_X_FORWARDED_FOR").split(",").first
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -6,7 +6,7 @@ module Keycard::Request
|
|
|
6
6
|
# values into the application environment to be accessed as usual.
|
|
7
7
|
class DirectAttributes < Attributes
|
|
8
8
|
def user_pid
|
|
9
|
-
get
|
|
9
|
+
get "REMOTE_USER"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def user_eid
|
|
@@ -14,7 +14,7 @@ module Keycard::Request
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def client_ip
|
|
17
|
-
safe(
|
|
17
|
+
safe("REMOTE_ADDR").split(",").first
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -10,7 +10,7 @@ module Keycard::Request
|
|
|
10
10
|
# HTTP_X_FORWARDED_FOR once the Rack request is assembled.
|
|
11
11
|
class ProxiedAttributes < Attributes
|
|
12
12
|
def user_pid
|
|
13
|
-
get
|
|
13
|
+
get "HTTP_X_REMOTE_USER"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def user_eid
|
|
@@ -18,7 +18,7 @@ module Keycard::Request
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def client_ip
|
|
21
|
-
safe(
|
|
21
|
+
safe("HTTP_X_FORWARDED_FOR").split(",").first
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -9,17 +9,17 @@ module Keycard::Request
|
|
|
9
9
|
# attributes guaranteed to have usable values are the client_ip, for all
|
|
10
10
|
# requests, and the user_pid, for requests from authenticated users.
|
|
11
11
|
class ShibbolethAttributes < Attributes
|
|
12
|
-
def base
|
|
12
|
+
def base
|
|
13
13
|
super.merge(
|
|
14
14
|
{
|
|
15
|
-
persistentNameID:
|
|
16
|
-
eduPersonPrincipalName:
|
|
15
|
+
persistentNameID: persistent_id,
|
|
16
|
+
eduPersonPrincipalName: principal_name,
|
|
17
17
|
eduPersonScopedAffiliation: affiliation,
|
|
18
|
-
displayName:
|
|
19
|
-
mail:
|
|
20
|
-
authnContextClassRef:
|
|
21
|
-
authenticationMethod:
|
|
22
|
-
identity_provider:
|
|
18
|
+
displayName: display_name,
|
|
19
|
+
mail: email,
|
|
20
|
+
authnContextClassRef: authn_context,
|
|
21
|
+
authenticationMethod: authn_method,
|
|
22
|
+
identity_provider: identity_provider
|
|
23
23
|
}
|
|
24
24
|
)
|
|
25
25
|
end
|
|
@@ -33,39 +33,39 @@ module Keycard::Request
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def client_ip
|
|
36
|
-
safe(
|
|
36
|
+
safe("HTTP_X_FORWARDED_FOR").split(",").first
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def persistent_id
|
|
40
|
-
get
|
|
40
|
+
get "HTTP_X_SHIB_PERSISTENT_ID"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def principal_name
|
|
44
|
-
get
|
|
44
|
+
get "HTTP_X_SHIB_EDUPERSONPRINCIPALNAME"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def display_name
|
|
48
|
-
get
|
|
48
|
+
get "HTTP_X_SHIB_DISPLAYNAME"
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def email
|
|
52
|
-
get
|
|
52
|
+
get "HTTP_X_SHIB_MAIL"
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def affiliation
|
|
56
|
-
safe(
|
|
56
|
+
safe("HTTP_X_SHIB_EDUPERSONSCOPEDAFFILIATION").split(";")
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def authn_method
|
|
60
|
-
get
|
|
60
|
+
get "HTTP_X_SHIB_AUTHENTICATION_METHOD"
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def authn_context
|
|
64
|
-
get
|
|
64
|
+
get "HTTP_X_SHIB_AUTHNCONTEXT_CLASS"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def identity_provider
|
|
68
|
-
get
|
|
68
|
+
get "HTTP_X_SHIB_IDENTITY_PROVIDER"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def identity_keys
|
data/lib/keycard/request.rb
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
module Keycard::Request
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
12
|
-
require_relative
|
|
7
|
+
require_relative "request/attributes"
|
|
8
|
+
require_relative "request/cosign_attributes"
|
|
9
|
+
require_relative "request/direct_attributes"
|
|
10
|
+
require_relative "request/proxied_attributes"
|
|
11
|
+
require_relative "request/shibboleth_attributes"
|
|
12
|
+
require_relative "request/attributes_factory"
|
data/lib/keycard/token.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Holds utility methods for parsing tokens from header values
|
|
4
4
|
class Keycard::Token
|
|
5
|
-
TOKEN_DELIMS = /\s*[:,;\t]\s
|
|
5
|
+
TOKEN_DELIMS = /\s*[:,;\t]\s*/
|
|
6
6
|
|
|
7
7
|
class << self
|
|
8
8
|
def rfc7235(string)
|
|
9
9
|
string
|
|
10
|
-
.sub(/^(Bearer|Token):?/,
|
|
10
|
+
.sub(/^(Bearer|Token):?/, "")
|
|
11
11
|
.split(TOKEN_DELIMS)
|
|
12
12
|
.map { |assignment| split_assignment(assignment) }
|
|
13
13
|
.to_h["token"]
|
|
@@ -19,8 +19,8 @@ class Keycard::Token
|
|
|
19
19
|
# @return An array of pairs of key:value, both strings
|
|
20
20
|
def split_assignment(string_assignment)
|
|
21
21
|
clean_assignment(string_assignment)
|
|
22
|
-
.split(
|
|
23
|
-
.push(
|
|
22
|
+
.split("=")
|
|
23
|
+
.push("")
|
|
24
24
|
.slice(0, 2)
|
|
25
25
|
end
|
|
26
26
|
|
data/lib/keycard/version.rb
CHANGED
data/lib/keycard.rb
CHANGED
data/lib/tasks/migrate.rake
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "keycard"
|
|
5
5
|
|
|
6
6
|
if defined?(Rails)
|
|
7
7
|
# When db:schema:dump is called directly, we can tack this on.
|
|
8
8
|
# If we do it unconditionally, db:migrate will try to dump before we have
|
|
9
9
|
# been able to migrate the Keycard tables.
|
|
10
|
-
if Rake.application.top_level_tasks.include?(
|
|
11
|
-
Rake::Task[
|
|
12
|
-
Rake::Task[
|
|
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
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -17,13 +17,13 @@ if defined?(Rails)
|
|
|
17
17
|
# schema_info, so migrations don't try to double-run. The actual table
|
|
18
18
|
# structure is handled by the Rails schema:dump and schema:load.
|
|
19
19
|
# A db:setup will trigger this, so we don't have to handle it separately.
|
|
20
|
-
Rake::Task[
|
|
21
|
-
Rake::Task[
|
|
20
|
+
Rake::Task["db:schema:load"].enhance do
|
|
21
|
+
Rake::Task["keycard:schema:load"].invoke
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# We hook into db:migrate for convenience.
|
|
25
|
-
Rake::Task[
|
|
26
|
-
Rake::Task[
|
|
25
|
+
Rake::Task["db:migrate"].enhance do
|
|
26
|
+
Rake::Task["keycard:migrate"].invoke
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
end
|
|
@@ -36,7 +36,7 @@ namespace :keycard do
|
|
|
36
36
|
# The Railtie is smart enough to know whether we are in a Rake task,
|
|
37
37
|
# so it can avoid initializing and we can migrate safely before the
|
|
38
38
|
# models are loaded.
|
|
39
|
-
Rake::Task[
|
|
39
|
+
Rake::Task["environment"].invoke
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# After migrating, we initialize here, even though it isn't strictly
|
|
@@ -54,21 +54,21 @@ namespace :keycard do
|
|
|
54
54
|
namespace :schema do
|
|
55
55
|
desc "Dump the Keycard version to db/keycard.yml"
|
|
56
56
|
task :dump do
|
|
57
|
-
Rake::Task[
|
|
57
|
+
Rake::Task["environment"].invoke
|
|
58
58
|
Keycard::DB.dump_schema!
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
desc "Load the Keycard version from db/keycard.yml"
|
|
62
62
|
task :load do
|
|
63
|
-
Rake::Task[
|
|
63
|
+
Rake::Task["environment"].invoke
|
|
64
64
|
Keycard::DB.load_schema!
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# When running under Rails, we dump the schema after migrating so
|
|
68
68
|
# everything stays synced up for db:setup against a new database.
|
|
69
69
|
# Rake::Task['keycard:schema:dump'].invoke
|
|
70
|
-
Rake::Task[
|
|
71
|
-
Rake::Task[
|
|
70
|
+
Rake::Task["keycard:migrate"].enhance do
|
|
71
|
+
Rake::Task["keycard:schema:dump"].invoke
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keycard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Noah Botimer
|
|
8
8
|
- Aaron Elkiss
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: exe
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: sequel
|
|
@@ -40,7 +39,7 @@ dependencies:
|
|
|
40
39
|
- !ruby/object:Gem::Version
|
|
41
40
|
version: '0'
|
|
42
41
|
- !ruby/object:Gem::Dependency
|
|
43
|
-
name:
|
|
42
|
+
name: logger
|
|
44
43
|
requirement: !ruby/object:Gem::Requirement
|
|
45
44
|
requirements:
|
|
46
45
|
- - ">="
|
|
@@ -54,7 +53,7 @@ dependencies:
|
|
|
54
53
|
- !ruby/object:Gem::Version
|
|
55
54
|
version: '0'
|
|
56
55
|
- !ruby/object:Gem::Dependency
|
|
57
|
-
name:
|
|
56
|
+
name: simplecov
|
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
|
59
58
|
requirements:
|
|
60
59
|
- - ">="
|
|
@@ -68,7 +67,7 @@ dependencies:
|
|
|
68
67
|
- !ruby/object:Gem::Version
|
|
69
68
|
version: '0'
|
|
70
69
|
- !ruby/object:Gem::Dependency
|
|
71
|
-
name:
|
|
70
|
+
name: simplecov-lcov
|
|
72
71
|
requirement: !ruby/object:Gem::Requirement
|
|
73
72
|
requirements:
|
|
74
73
|
- - ">="
|
|
@@ -82,7 +81,7 @@ dependencies:
|
|
|
82
81
|
- !ruby/object:Gem::Version
|
|
83
82
|
version: '0'
|
|
84
83
|
- !ruby/object:Gem::Dependency
|
|
85
|
-
name:
|
|
84
|
+
name: ostruct
|
|
86
85
|
requirement: !ruby/object:Gem::Requirement
|
|
87
86
|
requirements:
|
|
88
87
|
- - ">="
|
|
@@ -96,7 +95,7 @@ dependencies:
|
|
|
96
95
|
- !ruby/object:Gem::Version
|
|
97
96
|
version: '0'
|
|
98
97
|
- !ruby/object:Gem::Dependency
|
|
99
|
-
name:
|
|
98
|
+
name: pry
|
|
100
99
|
requirement: !ruby/object:Gem::Requirement
|
|
101
100
|
requirements:
|
|
102
101
|
- - ">="
|
|
@@ -110,7 +109,7 @@ dependencies:
|
|
|
110
109
|
- !ruby/object:Gem::Version
|
|
111
110
|
version: '0'
|
|
112
111
|
- !ruby/object:Gem::Dependency
|
|
113
|
-
name:
|
|
112
|
+
name: rake
|
|
114
113
|
requirement: !ruby/object:Gem::Requirement
|
|
115
114
|
requirements:
|
|
116
115
|
- - ">="
|
|
@@ -123,20 +122,48 @@ dependencies:
|
|
|
123
122
|
- - ">="
|
|
124
123
|
- !ruby/object:Gem::Version
|
|
125
124
|
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rspec
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: standard
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1.53'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.53'
|
|
126
153
|
- !ruby/object:Gem::Dependency
|
|
127
154
|
name: sqlite3
|
|
128
155
|
requirement: !ruby/object:Gem::Requirement
|
|
129
156
|
requirements:
|
|
130
157
|
- - "~>"
|
|
131
158
|
- !ruby/object:Gem::Version
|
|
132
|
-
version:
|
|
159
|
+
version: 2.9.0
|
|
133
160
|
type: :development
|
|
134
161
|
prerelease: false
|
|
135
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
136
163
|
requirements:
|
|
137
164
|
- - "~>"
|
|
138
165
|
- !ruby/object:Gem::Version
|
|
139
|
-
version:
|
|
166
|
+
version: 2.9.0
|
|
140
167
|
- !ruby/object:Gem::Dependency
|
|
141
168
|
name: yard
|
|
142
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -151,7 +178,6 @@ dependencies:
|
|
|
151
178
|
- - ">="
|
|
152
179
|
- !ruby/object:Gem::Version
|
|
153
180
|
version: '0'
|
|
154
|
-
description:
|
|
155
181
|
email:
|
|
156
182
|
- botimer@umich.edu
|
|
157
183
|
- aelkiss@umich.edu
|
|
@@ -160,17 +186,21 @@ extensions: []
|
|
|
160
186
|
extra_rdoc_files: []
|
|
161
187
|
files:
|
|
162
188
|
- ".envrc"
|
|
189
|
+
- ".github/dependabot.yml"
|
|
190
|
+
- ".github/workflows/test.yml"
|
|
163
191
|
- ".gitignore"
|
|
164
192
|
- ".rspec"
|
|
165
|
-
- ".
|
|
166
|
-
-
|
|
193
|
+
- ".standard.yml"
|
|
194
|
+
- CHANGELOG.md
|
|
167
195
|
- Gemfile
|
|
168
196
|
- LICENSE.md
|
|
169
197
|
- README.md
|
|
170
198
|
- Rakefile
|
|
171
199
|
- bin/console
|
|
172
200
|
- bin/rake
|
|
201
|
+
- bin/rspec
|
|
173
202
|
- bin/setup
|
|
203
|
+
- bin/standardrb
|
|
174
204
|
- db/migrations/1_create_tables.rb
|
|
175
205
|
- docs/Makefile
|
|
176
206
|
- docs/_static/.gitkeep
|
|
@@ -208,7 +238,6 @@ homepage: https://github.com/mlibrary/keycard
|
|
|
208
238
|
licenses:
|
|
209
239
|
- BSD-3-Clause
|
|
210
240
|
metadata: {}
|
|
211
|
-
post_install_message:
|
|
212
241
|
rdoc_options: []
|
|
213
242
|
require_paths:
|
|
214
243
|
- lib
|
|
@@ -216,16 +245,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
216
245
|
requirements:
|
|
217
246
|
- - ">="
|
|
218
247
|
- !ruby/object:Gem::Version
|
|
219
|
-
version:
|
|
248
|
+
version: 3.2.0
|
|
220
249
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
221
250
|
requirements:
|
|
222
251
|
- - ">="
|
|
223
252
|
- !ruby/object:Gem::Version
|
|
224
253
|
version: '0'
|
|
225
254
|
requirements: []
|
|
226
|
-
|
|
227
|
-
rubygems_version: 2.7.6.2
|
|
228
|
-
signing_key:
|
|
255
|
+
rubygems_version: 4.0.3
|
|
229
256
|
specification_version: 4
|
|
230
257
|
summary: Keycard provides authentication support and user/request information, especially
|
|
231
258
|
in Rails applications.
|
data/.rubocop.yml
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
-
Naming/UncommunicativeMethodParamName:
|
|
30
|
-
AllowedNames: ['db']
|
|
31
|
-
|
|
32
|
-
Style/StringLiterals:
|
|
33
|
-
Enabled: false
|
|
34
|
-
|
|
35
|
-
Style/ClassAndModuleChildren:
|
|
36
|
-
EnforcedStyle: compact
|