app_identity 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|