app_identity 1.0.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/.rdoc_options +28 -0
- data/Changelog.md +5 -0
- data/Contributing.md +70 -0
- data/Licence.md +25 -0
- data/Manifest.txt +30 -0
- data/README.md +70 -0
- data/Rakefile +50 -0
- data/bin/app-identity-suite-ruby +12 -0
- data/lib/app_identity/app.rb +214 -0
- data/lib/app_identity/error.rb +42 -0
- data/lib/app_identity/faraday_middleware.rb +94 -0
- data/lib/app_identity/internal.rb +130 -0
- data/lib/app_identity/rack_middleware.rb +242 -0
- data/lib/app_identity/validation.rb +83 -0
- data/lib/app_identity/versions.rb +194 -0
- data/lib/app_identity.rb +233 -0
- data/licences/APACHE-2.0.txt +168 -0
- data/licences/DCO.txt +34 -0
- data/spec.md +409 -0
- data/support/app_identity/suite/generator.rb +242 -0
- data/support/app_identity/suite/optional.json +491 -0
- data/support/app_identity/suite/program.rb +204 -0
- data/support/app_identity/suite/required.json +514 -0
- data/support/app_identity/suite/runner.rb +132 -0
- data/support/app_identity/suite.rb +10 -0
- data/support/app_identity/support.rb +119 -0
- data/test/minitest_helper.rb +24 -0
- data/test/test_app_identity.rb +124 -0
- data/test/test_app_identity_app.rb +64 -0
- data/test/test_app_identity_rack_middleware.rb +90 -0
- metadata +306 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 30f775dedcf26d6e5951cc7af9440ee560a76529946610300284bdc727d17972
|
4
|
+
data.tar.gz: 1017621f7d5474e23e8823a3d568b3dca11baef8e42a8f43441512205ab54620
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f2a942c09c6aa58a25c97552659d7848c2eea717bc94a752b28812ca951234931ad4e233abb7e765b5ce7b5b9574fe050e314ed69a0083fd18cdbe707f9dc9d
|
7
|
+
data.tar.gz: 7b38ec69bad5c20c2360495267944c9172f61475df4cfb8413c241b9c4a821f09ecf1a33a09b6f2a49156ee28a5882af83b630b20e055693e70d5084d277f311
|
data/.rdoc_options
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
--- !ruby/object:RDoc::Options
|
2
|
+
encoding: UTF-8
|
3
|
+
static_path: []
|
4
|
+
rdoc_include:
|
5
|
+
- "."
|
6
|
+
charset: UTF-8
|
7
|
+
exclude:
|
8
|
+
- Manifest.txt\z
|
9
|
+
- ~\z
|
10
|
+
- \.orig\z
|
11
|
+
- \.rej\z
|
12
|
+
- \.bak\z
|
13
|
+
- \.gemspec\z
|
14
|
+
hyperlink_all: false
|
15
|
+
line_numbers: false
|
16
|
+
locale:
|
17
|
+
locale_dir: locale
|
18
|
+
locale_name:
|
19
|
+
main_page:
|
20
|
+
markup: markdown
|
21
|
+
output_decoration: true
|
22
|
+
page_dir:
|
23
|
+
show_hash: false
|
24
|
+
tab_width: 8
|
25
|
+
template_stylesheets: []
|
26
|
+
title:
|
27
|
+
visibility: :protected
|
28
|
+
webcvs:
|
data/Changelog.md
ADDED
data/Contributing.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
We value contributions to AppIdentity for Ruby—bug reports, discussions, feature
|
4
|
+
requests, and code contributions. New features should be proposed and
|
5
|
+
[discussed][] prior to implementation, and release of any new feature may be
|
6
|
+
delayed until implemented in the three reference implementations.
|
7
|
+
|
8
|
+
Before contributing patches, please read the [Licence.md](licence.md).
|
9
|
+
|
10
|
+
App Identity is governed under the Kinetic Commerce Open Source [Code of
|
11
|
+
Conduct][].
|
12
|
+
|
13
|
+
## Code Guidelines
|
14
|
+
|
15
|
+
Our usual code contribution guidelines apply:
|
16
|
+
|
17
|
+
- Code changes _will not_ be accepted without tests. The test suite is written
|
18
|
+
with [minitest][].
|
19
|
+
- Match our coding style. We use [standard Ruby][] to assist with this.
|
20
|
+
- Use a thoughtfully-named topic branch that contains your change. Rebase your
|
21
|
+
commits into logical chunks as necessary.
|
22
|
+
- Use [quality commit messages][].
|
23
|
+
- Certain things must not be changed except as part of the release process. These
|
24
|
+
are:
|
25
|
+
- the version number in `lib/app_identity.rb`
|
26
|
+
- `Gemfile` (this is a stub file)
|
27
|
+
- `app_identity.gemspec` (this is a generated file)
|
28
|
+
- Submit a pull request with your changes.
|
29
|
+
- New or changed behaviours require new or updated documentation.
|
30
|
+
- New dependencies are discouraged.
|
31
|
+
|
32
|
+
There are code quality checks performed in GitHub Actions that must pass for any
|
33
|
+
pull request to be accepted.
|
34
|
+
|
35
|
+
## Test Dependencies
|
36
|
+
|
37
|
+
`app_identity` uses Ryan Davis’s [Hoe][] to manage the release process, and it
|
38
|
+
adds a number of useful rake tasks.
|
39
|
+
|
40
|
+
```console
|
41
|
+
$ rake
|
42
|
+
Run options: --seed 45847
|
43
|
+
|
44
|
+
# Running:
|
45
|
+
|
46
|
+
..............................
|
47
|
+
|
48
|
+
Finished in 0.005884s, 5098.5724 runs/s, 10197.1448 assertions/s.
|
49
|
+
|
50
|
+
30 runs, 60 assertions, 0 failures, 0 errors, 0 skips
|
51
|
+
rm -rf doc
|
52
|
+
rm -r pkg
|
53
|
+
```
|
54
|
+
|
55
|
+
We have provided the simplest possible Gemfile pointing to the (generated)
|
56
|
+
`app_identity.gemspec` file. This will permit you to use `bundle install` to get
|
57
|
+
the development dependencies.
|
58
|
+
|
59
|
+
```console
|
60
|
+
$ bundle install
|
61
|
+
…
|
62
|
+
Bundle complete!
|
63
|
+
```
|
64
|
+
|
65
|
+
[code of conduct]: https://github.com/KineticCafe/code-of-conduct
|
66
|
+
[discussed]: https://github.com/KineticCafe/app_identity/discussions
|
67
|
+
[hoe]: https://github.com/seattlerb/hoe
|
68
|
+
[minitest]: https://github.com/seattlerb/minitest
|
69
|
+
[quality commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
70
|
+
[standard ruby]: https://github.com/testdouble/standard
|
data/Licence.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Licence
|
2
|
+
|
3
|
+
App Identity for Ruby is copyright © 2022 Kinetic Commerce and contributors
|
4
|
+
and is licensed under [Apache Licence, version 2.0][apache-licence-20].
|
5
|
+
|
6
|
+
## Developer Certificate of Origin
|
7
|
+
|
8
|
+
All contributors **must** certify they are able and willing to provide their
|
9
|
+
contributions under the terms of this project's licenses with the certification
|
10
|
+
of the [Developer Certificate of Origin (Version 1.1)][dco].
|
11
|
+
|
12
|
+
Such certification is provided by ensuring that the following line must be
|
13
|
+
included as the last line of a commit message for every commit contributed:
|
14
|
+
|
15
|
+
Signed-off-by: FirstName LastName <email@example.org>
|
16
|
+
|
17
|
+
The `Signed-off-by` line can be automatically added by git with the `-s` or
|
18
|
+
`--signoff` option on `git commit`:
|
19
|
+
|
20
|
+
```sh
|
21
|
+
git commit --signoff
|
22
|
+
```
|
23
|
+
|
24
|
+
[apache-licence-20]: licences/APAHCE-2.0.txt
|
25
|
+
[dco]: licenses/dco.txt
|
data/Manifest.txt
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
.rdoc_options
|
2
|
+
Changelog.md
|
3
|
+
Contributing.md
|
4
|
+
Licence.md
|
5
|
+
Manifest.txt
|
6
|
+
README.md
|
7
|
+
Rakefile
|
8
|
+
bin/app-identity-suite-ruby
|
9
|
+
lib/app_identity.rb
|
10
|
+
lib/app_identity/app.rb
|
11
|
+
lib/app_identity/error.rb
|
12
|
+
lib/app_identity/faraday_middleware.rb
|
13
|
+
lib/app_identity/internal.rb
|
14
|
+
lib/app_identity/rack_middleware.rb
|
15
|
+
lib/app_identity/validation.rb
|
16
|
+
lib/app_identity/versions.rb
|
17
|
+
licences/APACHE-2.0.txt
|
18
|
+
licences/DCO.txt
|
19
|
+
spec.md
|
20
|
+
support/app_identity/suite.rb
|
21
|
+
support/app_identity/suite/generator.rb
|
22
|
+
support/app_identity/suite/optional.json
|
23
|
+
support/app_identity/suite/program.rb
|
24
|
+
support/app_identity/suite/required.json
|
25
|
+
support/app_identity/suite/runner.rb
|
26
|
+
support/app_identity/support.rb
|
27
|
+
test/minitest_helper.rb
|
28
|
+
test/test_app_identity.rb
|
29
|
+
test/test_app_identity_app.rb
|
30
|
+
test/test_app_identity_rack_middleware.rb
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# AppIdentity for Ruby
|
2
|
+
|
3
|
+
- code :: https://github.com/KineticCafe/app-identity/tree/main/ruby/
|
4
|
+
- issues :: https://github.com/KineticCafe/app-identity/issues
|
5
|
+
|
6
|
+
## Description
|
7
|
+
|
8
|
+
AppIdentity is a Ruby implementation of the Kinetic Commerce application
|
9
|
+
identity proof algorithm as described in its [spec][].
|
10
|
+
|
11
|
+
## Synopsis
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
app = AppIdentity::App.new(id: id, secret: secret, version: 2)
|
15
|
+
proof = AppIdentity.generate_proof!(app)
|
16
|
+
AppIdentity.verify_proof!(proof, app)
|
17
|
+
```
|
18
|
+
|
19
|
+
In a Rails application, proof verification would use
|
20
|
+
`AppIdentity::RackMiddleware.`
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'app_identity/rack_middleware'
|
24
|
+
|
25
|
+
config.middleware.use AppIdentity::RackMiddleware,
|
26
|
+
header: "app-identity-proof",
|
27
|
+
finder: ->(proof) { IdentityApplication.find(proof[:id]) }
|
28
|
+
```
|
29
|
+
|
30
|
+
There is a Faraday Middleware for providing proof generation for clients.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Faraday.new(url: url) do |conn|
|
34
|
+
conn.request :app_identity,dentity_app: app,
|
35
|
+
header: 'app-proof-identity'
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
Add `app_identity` to your Gemfile:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
gem 'app_identity', '~> 1.0'
|
45
|
+
```
|
46
|
+
|
47
|
+
## Semantic Versioning
|
48
|
+
|
49
|
+
`AppIdentity` uses a [Semantic Versioning][] scheme with one significant change:
|
50
|
+
|
51
|
+
- When PATCH is zero (`0`), it will be omitted from version references.
|
52
|
+
|
53
|
+
Additionally, the major version will generally be reserved for specification
|
54
|
+
revisions.
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
AppIdentity for Ruby [welcomes contributions](Contributing.md). This project,
|
59
|
+
all Kinetic Commerce [open source projects][], is under the Kinetic Commerce
|
60
|
+
Open Source [Code of Conduct][kccoc].
|
61
|
+
|
62
|
+
AppIdentity for Ruby is licensed under the Apache Licence, version 2.0 and
|
63
|
+
requires certification via a Developer Certificate of Origin. See
|
64
|
+
[Licence.md](Licence.md) for more details.
|
65
|
+
|
66
|
+
[contributing]: Contributing.md
|
67
|
+
[kccoc]: https://github.com/KineticCafe/code-of-conduct
|
68
|
+
[open source projects]: https://github.com/KineticCafe
|
69
|
+
[semantic versioning]: http://semver.org/
|
70
|
+
[spec]: https://github.com/KineticCafe/app-identity/blob/main/spec/README.md
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "hoe"
|
5
|
+
require "rake/clean"
|
6
|
+
|
7
|
+
Hoe.plugin :doofus
|
8
|
+
Hoe.plugin :gemspec2
|
9
|
+
Hoe.plugin :git2
|
10
|
+
Hoe.plugin :minitest
|
11
|
+
|
12
|
+
Hoe.spec "app_identity" do
|
13
|
+
developer("Austin Ziegler", "aziegler@kineticcommerce.com")
|
14
|
+
developer("Kinetic Commerce", "dev@kineticcommerce.com")
|
15
|
+
|
16
|
+
self.history_file = "Changelog.md"
|
17
|
+
self.readme_file = "README.md"
|
18
|
+
|
19
|
+
license "MIT"
|
20
|
+
|
21
|
+
spec_extras[:metadata] = ->(val) { val["rubygems_mfa_required"] = "true" }
|
22
|
+
|
23
|
+
extra_deps << ["optimist", "~> 3.0"]
|
24
|
+
extra_dev_deps << ["hoe-doofus", "~> 1.0"]
|
25
|
+
extra_dev_deps << ["hoe-gemspec2", "~> 1.1"]
|
26
|
+
extra_dev_deps << ["hoe-git2", "~> 1.7"]
|
27
|
+
extra_dev_deps << ["minitest", "~> 5.4"]
|
28
|
+
extra_dev_deps << ["minitest-autotest", "~> 1.0"]
|
29
|
+
extra_dev_deps << ["minitest-bisect", "~> 1.2"]
|
30
|
+
extra_dev_deps << ["minitest-focus", "~> 1.1"]
|
31
|
+
extra_dev_deps << ["minitest-pretty_diff", "~> 0.1"]
|
32
|
+
extra_dev_deps << ["rack-test", "~> 0.6"]
|
33
|
+
extra_dev_deps << ["rake", ">= 10.0", "< 14.0"]
|
34
|
+
extra_dev_deps << ["rdoc", "~> 6.4"]
|
35
|
+
extra_dev_deps << ["simplecov", "~> 0.7"]
|
36
|
+
extra_dev_deps << ["standard", "~> 1.0"]
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace :console do
|
40
|
+
task :pry do
|
41
|
+
exec "pry -Ilib -rapp_identity"
|
42
|
+
end
|
43
|
+
|
44
|
+
task :irb do
|
45
|
+
exec "irb -Ilib -rapp_identity"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Open a console with AppIdentity loaded"
|
50
|
+
task console: "console:irb"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
root = File.expand_path("../../", __FILE__)
|
4
|
+
|
5
|
+
if File.exist?(File.join(root, "app_identity.gemspec"))
|
6
|
+
$LOAD_PATH.unshift(File.join(root, "lib"), File.join(root, "support"))
|
7
|
+
end
|
8
|
+
|
9
|
+
require "optimist"
|
10
|
+
require "app_identity/suite"
|
11
|
+
|
12
|
+
exit 1 unless AppIdentity::Suite::Program.run(name: File.basename(__FILE__))
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
require_relative "error"
|
6
|
+
require_relative "validation"
|
7
|
+
|
8
|
+
# The class used by the App Identity proof generation and verification
|
9
|
+
# algorithms. This will typically be constructed from another object, structure,
|
10
|
+
# or hash, such as from a static configuration file or a database record.
|
11
|
+
#
|
12
|
+
# AppIdentity::App objects are created frozen, and certain operations may
|
13
|
+
# provide a modified duplicate.
|
14
|
+
class AppIdentity::App
|
15
|
+
def self.new(input) # :nodoc:
|
16
|
+
if input.is_a?(AppIdentity::App) && !input.verified
|
17
|
+
input
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include AppIdentity::Validation
|
24
|
+
|
25
|
+
# The AppIdentity App unique identifier. Validation of the `id` value will
|
26
|
+
# convert non-string IDs using #to_s.
|
27
|
+
#
|
28
|
+
# If using integer IDs, it is recommended that the `id` value be provided as
|
29
|
+
# some form of extended string value, such as that provided by Rails [global
|
30
|
+
# ID](https://github.com/rails/globalid). Such representations are _also_
|
31
|
+
# recommended if the ID is a compound value.
|
32
|
+
#
|
33
|
+
# `id` values _must not_ contain a colon (`:`) character.
|
34
|
+
attr_reader :id
|
35
|
+
|
36
|
+
# The App Identity app secret value. This value is used _as provided_ with no
|
37
|
+
# encoding or decoding. As this is a sensitive value, it may be provided
|
38
|
+
# as a 0-arity closure proc.
|
39
|
+
#
|
40
|
+
# For security purposes, this is always stored as a 0-arity closure proc.
|
41
|
+
attr_reader :secret
|
42
|
+
|
43
|
+
# The positive integer version of the AppIdentity algorithm to use. Will be
|
44
|
+
# validated to be a supported version for app creation, and not an explicitly
|
45
|
+
# disallowed version during proof validation.
|
46
|
+
#
|
47
|
+
# A string `version` must convert cleanly to an integer value, meaning that
|
48
|
+
# `"3.5"` is not a valid value.
|
49
|
+
#
|
50
|
+
# AppIdentity algorithm versions are strictly upgradeable. That is, a version
|
51
|
+
# 1 app can verify version 1, 2, 3, or 4 proofs. However, a version 2 app will
|
52
|
+
# _never_ validate a version 1 proof.
|
53
|
+
#
|
54
|
+
# <table>
|
55
|
+
# <thead>
|
56
|
+
# <tr>
|
57
|
+
# <th rowspan=2>Version</th>
|
58
|
+
# <th rowspan=2>Nonce</th>
|
59
|
+
# <th rowspan=2>Digest Algorithm</th>
|
60
|
+
# <th colspan=4>Can Verify</th>
|
61
|
+
# </tr>
|
62
|
+
# <tr><th>1</th><th>2</th><th>3</th><th>4</th></tr>
|
63
|
+
# </thead>
|
64
|
+
# <tbody>
|
65
|
+
# <tr><th>1</th><td>random</td><td>SHA 256</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr>
|
66
|
+
# <tr><th>2</th><td>timestamp ± fuzz</td><td>SHA 256</td><td>⛔️</td><td>✅</td><td>✅</td><td>✅</td></tr>
|
67
|
+
# <tr><th>3</th><td>timestamp ± fuzz</td><td>SHA 384</td><td>⛔️</td><td>⛔️</td><td>✅</td><td>✅</td></tr>
|
68
|
+
# <tr><th>4</th><td>timestamp ± fuzz</td><td>SHA 512</td><td>⛔️</td><td>⛔️</td><td>⛔️</td><td>✅</td></tr>
|
69
|
+
# </tbody>
|
70
|
+
# </table>
|
71
|
+
attr_reader :version
|
72
|
+
|
73
|
+
# An optional configuration value for validation of an App Identity proof.
|
74
|
+
#
|
75
|
+
# If not provided, the default value when required is `{fuzz: 600}`,
|
76
|
+
# specifying that the timestamp may not differ from the current time by more
|
77
|
+
# than ±600 seconds (±10 minutes). Depending on the nature of the app being
|
78
|
+
# verified and the expected network conditions, a shorter time period than 600
|
79
|
+
# seconds is recommended.
|
80
|
+
#
|
81
|
+
# The App Identity version 1 algorithm does not use `config`.
|
82
|
+
attr_reader :config
|
83
|
+
|
84
|
+
# The original object used to construct this App Identity object.
|
85
|
+
attr_reader :source
|
86
|
+
|
87
|
+
# Whether this app was used in the successful verification of a proof.
|
88
|
+
attr_reader :verified
|
89
|
+
|
90
|
+
# Constructs an AppIdentity::App from a provided object or zero-arity
|
91
|
+
# callable that returns an initialization object. These values should be
|
92
|
+
# treated as immutable objects.
|
93
|
+
#
|
94
|
+
# The object must respond to `#id`, `#secret`, `#version`, and `#config` or
|
95
|
+
# have indexable keys (via `#[]`) of `id`, `secret`, `version`, and `config`
|
96
|
+
# as either Symbol or String values. That is, the `id` should be retrievable
|
97
|
+
# in one of the following ways:
|
98
|
+
#
|
99
|
+
# ```ruby
|
100
|
+
# input.id
|
101
|
+
# input[:id]
|
102
|
+
# input["id"]
|
103
|
+
# ```
|
104
|
+
#
|
105
|
+
# If the input parameter is a callable, it will be called with no
|
106
|
+
# parameters to produce an input object.
|
107
|
+
#
|
108
|
+
# The AppIdentity::App is frozen on creation.
|
109
|
+
#
|
110
|
+
# ```ruby
|
111
|
+
# AppIdentity::App.new({id: 1, secret: "secret", version: 1})
|
112
|
+
#
|
113
|
+
# AppIdentity::App.new(->() { {id: 1, secret: "secret", version: 1} })
|
114
|
+
# ```
|
115
|
+
#
|
116
|
+
# If the provided `input` is already an App and is not #verified, the existing
|
117
|
+
# app will be returned instead of creating a new application.
|
118
|
+
def initialize(input)
|
119
|
+
input = input.call if input.respond_to?(:call)
|
120
|
+
|
121
|
+
@id = get(input, :id)
|
122
|
+
@secret = fwrap(get(input, :secret).dup)
|
123
|
+
@version = get(input, :version)
|
124
|
+
@config = get(input, :config)
|
125
|
+
@source = input
|
126
|
+
@verified = false
|
127
|
+
|
128
|
+
validate!
|
129
|
+
freeze
|
130
|
+
end
|
131
|
+
|
132
|
+
# If the current App is not `verified`, then return a copy of the current App
|
133
|
+
# with the verified flag set to `true`.
|
134
|
+
def verify
|
135
|
+
verified ? self : dup.tap { |v| v.instance_variable_set(:@verified, true) }.freeze
|
136
|
+
end
|
137
|
+
|
138
|
+
# If the current App is `verified`, then return a copy of the current App
|
139
|
+
# with the verified flag set to `false`.
|
140
|
+
def unverify
|
141
|
+
verified ? dup.tap { |v| v.instance_variable_set(:@verified, false) }.freeze : self
|
142
|
+
end
|
143
|
+
|
144
|
+
# Generate a nonce for this application. Optionally provide a version number
|
145
|
+
# override to generate a compatible (upgraded) nonce version.
|
146
|
+
def generate_nonce(version = nil)
|
147
|
+
version ||= self.version
|
148
|
+
|
149
|
+
unless self.version <= version
|
150
|
+
raise "app version #{self.version} is not compatible with requested version #{version}"
|
151
|
+
end
|
152
|
+
|
153
|
+
AppIdentity::Versions[version].generate_nonce
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_h # :nodoc:
|
157
|
+
{config: config, id: id, secret: secret.call, version: version}
|
158
|
+
end
|
159
|
+
|
160
|
+
alias_method :as_json, :to_h
|
161
|
+
|
162
|
+
def to_s # :nodoc:
|
163
|
+
inspect
|
164
|
+
end
|
165
|
+
|
166
|
+
def to_json(*args, **kwargs) # :nodoc:
|
167
|
+
as_json.to_json(*args, **kwargs)
|
168
|
+
end
|
169
|
+
|
170
|
+
def hash # :nodoc:
|
171
|
+
[AppIdentityApp::App, id, version, config, secret]
|
172
|
+
end
|
173
|
+
|
174
|
+
def inspect # :nodoc:
|
175
|
+
"#<#{self.class} id: #{id} version: #{version} config: #{config} verified: #{verified}>"
|
176
|
+
end
|
177
|
+
|
178
|
+
def ==(other) # :nodoc:
|
179
|
+
other.is_a?(self.class) &&
|
180
|
+
id == other.id &&
|
181
|
+
version == other.version &&
|
182
|
+
config == other.config &&
|
183
|
+
verified == other.verified &&
|
184
|
+
secret.call == other.secret.call
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def get(input, key)
|
190
|
+
case input
|
191
|
+
when Hash, Struct, OpenStruct
|
192
|
+
input[key] || input[key.to_s]
|
193
|
+
else
|
194
|
+
if input.respond_to?(key)
|
195
|
+
input.__send__(key)
|
196
|
+
elsif input.respond_to?(:[]) && !input.is_a?(Array)
|
197
|
+
input[key] || input[key.to_s]
|
198
|
+
else
|
199
|
+
raise AppIdentity::Error, "app cannot be created from input (missing value #{key.inspect})"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def fwrap(value)
|
205
|
+
value.respond_to?(:call) ? value : -> { value }
|
206
|
+
end
|
207
|
+
|
208
|
+
def validate!
|
209
|
+
@id = validate_id(@id)
|
210
|
+
@secret = fwrap(validate_secret(@secret))
|
211
|
+
@version = validate_version(@version)
|
212
|
+
@config = validate_config(@config)
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An exception that will be raised by AppIdentity.generate_proof!,
|
4
|
+
# AppIdentity.parse_proof!, or AppIdentity.verify_proof! when those functions
|
5
|
+
# fail. The error message reported here is *always* generic.
|
6
|
+
#
|
7
|
+
# This can also be raised by AppIdentity::RackMiddleware on configuration
|
8
|
+
# failure.
|
9
|
+
class AppIdentity::Error < ::StandardError
|
10
|
+
attr_reader :message # :nodoc:
|
11
|
+
|
12
|
+
def initialize(type) # :nodoc:
|
13
|
+
@message = resolve(type)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def resolve(type)
|
19
|
+
case type
|
20
|
+
when String
|
21
|
+
type
|
22
|
+
when :verify_proof
|
23
|
+
"Error verifying proof"
|
24
|
+
when :generate_proof
|
25
|
+
"Error generating proof"
|
26
|
+
when :parse_proof
|
27
|
+
"Error parsing proof"
|
28
|
+
when :disallowed_configuration_error
|
29
|
+
"error in disallowed version configuration"
|
30
|
+
when :plug_missing_apps_or_finder
|
31
|
+
"AppIdentity::RackMiddleware configuration error: one of `apps` or `finder` options is required"
|
32
|
+
when :plug_headers_required
|
33
|
+
"AppIdentity::RackMiddleware configuration error: `headers` option is required"
|
34
|
+
when :plug_header_invalid
|
35
|
+
"AppIdentity::RackMiddleware configuration error: `headers` value is invalid"
|
36
|
+
when :plug_on_failure_invalid
|
37
|
+
"AppIdentity::RackMiddleware configuration error: `on_failure` value is invalid"
|
38
|
+
when :plug_disallowed_invalid
|
39
|
+
"AppIdentity::RackMiddleware configuration error: `disallowed` value is invalid"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if defined?(Faraday::Middleware)
|
4
|
+
require "app_identity"
|
5
|
+
|
6
|
+
# A Faraday middleware that generates an app identity proof header for
|
7
|
+
# a request.
|
8
|
+
#
|
9
|
+
# The `options` provided has the following parameters:
|
10
|
+
#
|
11
|
+
# - `app`: (required) An AppIdentity::App or object that can be coerced into
|
12
|
+
# an AppIdentity::app with AppIdentity#new.
|
13
|
+
#
|
14
|
+
# - `disallowed`: A list of algorithm versions that are not allowed when
|
15
|
+
# processing received identity proofs. See AppIdentity::Versions.allowed?.
|
16
|
+
#
|
17
|
+
# - `header`: (required) The header to use for sending the app identity proof.
|
18
|
+
#
|
19
|
+
# - `on_failure`: (optional) The action to take when an app identity proof
|
20
|
+
# cannot be generated for any reason. May be one of the following values:
|
21
|
+
#
|
22
|
+
# - `:fail`: Throws an exception. This is the default if `on_failure` is
|
23
|
+
# not specified.
|
24
|
+
#
|
25
|
+
# - `:pass`: Sets the header to the empty value returned. The request will
|
26
|
+
# probably fail on the receiving server side.
|
27
|
+
#
|
28
|
+
# - `:skip`: Does not add the header, as if the request were not made
|
29
|
+
# using an application.
|
30
|
+
#
|
31
|
+
# `on_failure` may also be provided a callable object that expects three
|
32
|
+
# parameters:
|
33
|
+
#
|
34
|
+
# - `env`: The Faraday middleware `env` value;
|
35
|
+
# - `app`: The identity app value provided to the middleware; and
|
36
|
+
# - `header`: The header name provided to the middleware.
|
37
|
+
#
|
38
|
+
# The callable may return either the `env`, `:fail`, `:skip`, or `:pass`. Any
|
39
|
+
# other value will be treated as `:fail`.
|
40
|
+
class AppIdentity::FaradayMiddleware < Faraday::Middleware
|
41
|
+
def initialize(app, options = {}) # :nodoc:
|
42
|
+
super(app)
|
43
|
+
|
44
|
+
@identity_app = AppIdentity::App.new(options.fetch(:app))
|
45
|
+
@header = options.fetch(:header).downcase
|
46
|
+
@on_failure = options.fetch(:on_failure, :fail)
|
47
|
+
@disallowed = options.fetch(:disallowed, nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(env) # :nodoc:
|
51
|
+
proof = AppIdentity.generate_proof(@identity_app, disallowed: @disallowed)
|
52
|
+
|
53
|
+
if proof.nil?
|
54
|
+
handle_failure(@on_failure)
|
55
|
+
else
|
56
|
+
env[:request_headers][@header] = proof
|
57
|
+
end
|
58
|
+
|
59
|
+
@app.call(env)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def handle_failure(on_failure)
|
65
|
+
case on_failure
|
66
|
+
when :skip
|
67
|
+
nil
|
68
|
+
when :pass
|
69
|
+
env[:request_headers][@header] = ""
|
70
|
+
when :fail
|
71
|
+
raise AppIdentity::Error, "unable to generate proof for app #{@identity_app.id}"
|
72
|
+
else
|
73
|
+
if on_failure.respond_to?(:call)
|
74
|
+
result = on_failure.call(env, @identity_app, @header)
|
75
|
+
|
76
|
+
case result
|
77
|
+
when :skip, :pass, :fail
|
78
|
+
return handle_failure(result)
|
79
|
+
else
|
80
|
+
if result.eql?(env)
|
81
|
+
return
|
82
|
+
else
|
83
|
+
return handle_failure(:fail)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
handle_failure(:fail)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Faraday::Request.register_middleware app_identity: -> { AppIdentity::FaradayMiddleware }
|
94
|
+
end
|