omniauth-identity 3.1.3 → 3.1.5
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -4
- data/CITATION.cff +20 -0
- data/CODE_OF_CONDUCT.md +3 -4
- data/CONTRIBUTING.md +129 -32
- data/FUNDING.md +66 -0
- data/README.md +290 -136
- data/REEK +0 -0
- data/RUBOCOP.md +71 -0
- data/SECURITY.md +6 -18
- data/lib/omniauth/identity/model.rb +29 -7
- data/lib/omniauth/identity/models/active_record.rb +46 -2
- data/lib/omniauth/identity/models/couch_potato.rb +56 -4
- data/lib/omniauth/identity/models/mongoid.rb +47 -2
- data/lib/omniauth/identity/models/nobrainer.rb +47 -3
- data/lib/omniauth/identity/models/rom.rb +151 -0
- data/lib/omniauth/identity/models/sequel.rb +71 -9
- data/lib/omniauth/identity/secure_password.rb +33 -0
- data/lib/omniauth/identity/version.rb +5 -1
- data/lib/omniauth/identity.rb +30 -0
- data/lib/omniauth/strategies/identity.rb +127 -6
- data.tar.gz.sig +0 -0
- metadata +164 -33
- metadata.gz.sig +0 -0
@@ -6,12 +6,41 @@ module OmniAuth
|
|
6
6
|
module Identity
|
7
7
|
module Models
|
8
8
|
# Sequel is an ORM adapter for the following databases:
|
9
|
-
#
|
9
|
+
# ADO, Amalgalite, IBM_DB, JDBC, MySQL, Mysql2, ODBC, Oracle, PostgreSQL, SQLAnywhere, SQLite3, and TinyTDS
|
10
10
|
# The homepage is: http://sequel.jeremyevans.net/
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
#
|
12
|
+
# This module provides OmniAuth Identity functionality for Sequel models,
|
13
|
+
# including secure password handling and authentication key management.
|
14
|
+
#
|
15
|
+
# @example Usage
|
16
|
+
# class User < Sequel::Model(:users)
|
17
|
+
# include OmniAuth::Identity::Models::Sequel
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # Schema example:
|
21
|
+
# # DB.create_table :users do
|
22
|
+
# # primary_key :id
|
23
|
+
# # String :email, null: false, unique: true
|
24
|
+
# # String :password_digest, null: false
|
25
|
+
# # end
|
26
|
+
#
|
27
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
28
|
+
# user.save
|
29
|
+
#
|
30
|
+
# # Authenticate a user
|
31
|
+
# authenticated_user = User.locate(email: 'user@example.com')
|
32
|
+
# authenticated_user.authenticate('password') # => user or false
|
33
|
+
#
|
34
|
+
# @note Sequel is *not* based on ActiveModel, but supports the API we need, except for `persisted?`.
|
35
|
+
# @note Validations are enabled by default in versions < 4, but may change to disabled in v4 if ActiveModel is not present.
|
14
36
|
module Sequel
|
37
|
+
# Called when this module is included in a model class.
|
38
|
+
#
|
39
|
+
# This method extends the base class with OmniAuth Identity functionality,
|
40
|
+
# including secure password support and authentication key validation.
|
41
|
+
#
|
42
|
+
# @param base [Class] the model class including this module
|
43
|
+
# @return [void]
|
15
44
|
def self.included(base)
|
16
45
|
base.class_eval do
|
17
46
|
# NOTE: Using the deprecated :validations_class_methods because it defines
|
@@ -30,26 +59,59 @@ module OmniAuth
|
|
30
59
|
has_secure_password(validations: OmniAuth::Identity::Version.major < 4)
|
31
60
|
|
32
61
|
class << self
|
62
|
+
# @!method self.auth_key=(key)
|
63
|
+
# Sets the authentication key for the model and adds uniqueness validation.
|
64
|
+
#
|
65
|
+
# @param key [Symbol, String] the attribute to use as the authentication key
|
66
|
+
# @return [void]
|
67
|
+
# @example
|
68
|
+
# class User < Sequel::Model(:users)
|
69
|
+
# include OmniAuth::Identity::Models::Sequel
|
70
|
+
# self.auth_key = :email
|
71
|
+
# end
|
33
72
|
def auth_key=(key)
|
34
73
|
super
|
35
74
|
# Sequel version of validates_uniqueness_of! Does not incur ActiveRecord dependency!
|
36
75
|
validates_uniqueness_of(:key, case_sensitive: false)
|
37
76
|
end
|
38
77
|
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
78
|
+
# @!method self.locate(arguments)
|
79
|
+
# Finds a record by the given search criteria.
|
80
|
+
#
|
81
|
+
# Filtering is probably the most common dataset modifying action done in Sequel.
|
82
|
+
# Both the where and filter methods filter the dataset by modifying the dataset's WHERE clause.
|
83
|
+
# Both accept a wide variety of input formats.
|
84
|
+
# See: https://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html#label-Filters
|
85
|
+
#
|
86
|
+
# @param arguments [any] the search criteria
|
87
|
+
# @return [Sequel::Model, nil] the first matching record or nil
|
88
|
+
# @example
|
89
|
+
# User.locate(email: 'user@example.com')
|
44
90
|
def locate(arguments)
|
45
91
|
where(arguments).first
|
46
92
|
end
|
47
93
|
end
|
48
94
|
|
95
|
+
# @!method persisted?
|
96
|
+
# Checks if the record exists in the database.
|
97
|
+
#
|
98
|
+
# @return [Boolean] true if the record exists, false otherwise
|
99
|
+
# @example
|
100
|
+
# user = User.new
|
101
|
+
# user.persisted? # => false
|
102
|
+
# user.save
|
103
|
+
# user.persisted? # => true
|
49
104
|
def persisted?
|
50
105
|
exists?
|
51
106
|
end
|
52
107
|
|
108
|
+
# @!method save
|
109
|
+
# Saves the record to the database.
|
110
|
+
#
|
111
|
+
# @return [Boolean] true if saved successfully, false otherwise
|
112
|
+
# @example
|
113
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
114
|
+
# user.save # => true
|
53
115
|
def save
|
54
116
|
super
|
55
117
|
end
|
@@ -9,21 +9,45 @@ module OmniAuth
|
|
9
9
|
# include SecurePassword. The only difference is that instead of
|
10
10
|
# using ActiveSupport::Concern, it checks to see if there is already
|
11
11
|
# a has_secure_password method.
|
12
|
+
#
|
13
|
+
# Provides secure password hashing and authentication using BCrypt.
|
14
|
+
#
|
15
|
+
# @example Basic Usage
|
16
|
+
# class User
|
17
|
+
# include OmniAuth::Identity::SecurePassword
|
18
|
+
#
|
19
|
+
# has_secure_password
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# user = User.new(password: 'secret')
|
23
|
+
# user.authenticate('secret') # => user
|
12
24
|
module SecurePassword
|
25
|
+
# Called when this module is included in a model class.
|
26
|
+
#
|
27
|
+
# Extends the base class with ClassMethods unless it already responds to has_secure_password.
|
28
|
+
#
|
29
|
+
# @param base [Class] the model class including this module
|
30
|
+
# @return [void]
|
13
31
|
def self.included(base)
|
14
32
|
base.extend(ClassMethods) unless base.respond_to?(:has_secure_password)
|
15
33
|
end
|
16
34
|
|
35
|
+
# @!attribute [r] MAX_PASSWORD_LENGTH_ALLOWED
|
17
36
|
# BCrypt hash function can handle maximum 72 bytes, and if we pass
|
18
37
|
# password of length more than 72 bytes it ignores extra characters.
|
19
38
|
# Hence need to put a restriction on password length.
|
39
|
+
# @return [Integer] The maximum allowed password length in bytes.
|
20
40
|
MAX_PASSWORD_LENGTH_ALLOWED = BCrypt::Engine::MAX_SECRET_BYTESIZE
|
21
41
|
|
22
42
|
class << self
|
43
|
+
# @!attribute [rw] min_cost
|
44
|
+
# Controls whether to use minimum cost for BCrypt hashing (for testing).
|
45
|
+
# @return [true, false]
|
23
46
|
attr_accessor :min_cost # :nodoc:
|
24
47
|
end
|
25
48
|
self.min_cost = false
|
26
49
|
|
50
|
+
# Class-level methods for secure password functionality.
|
27
51
|
module ClassMethods
|
28
52
|
# Adds methods to set and authenticate against a BCrypt password.
|
29
53
|
# This mechanism requires you to have a +XXX_digest+ attribute.
|
@@ -82,6 +106,10 @@ module OmniAuth
|
|
82
106
|
# user.authenticate_recovery_password('42password') # => user
|
83
107
|
# User.find_by(name: 'david')&.authenticate('notright') # => false
|
84
108
|
# User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
|
109
|
+
#
|
110
|
+
# @param attribute [Symbol, String] the attribute name for the password (default: :password)
|
111
|
+
# @param validations [true, false] whether to add validations (default: true)
|
112
|
+
# @return [void]
|
85
113
|
def has_secure_password(attribute = :password, validations: true)
|
86
114
|
# Load bcrypt gem only when has_secure_password is used.
|
87
115
|
# This is to avoid ActiveModel (and by extension the entire framework)
|
@@ -121,7 +149,12 @@ module OmniAuth
|
|
121
149
|
end
|
122
150
|
end
|
123
151
|
|
152
|
+
# A module that defines instance methods for password handling.
|
153
|
+
# Methods are defined dynamically based on the attribute name.
|
124
154
|
class InstanceMethodsOnActivation < Module
|
155
|
+
# Initializes the module with the password attribute name.
|
156
|
+
#
|
157
|
+
# @param attribute [Symbol, String] the password attribute name
|
125
158
|
def initialize(attribute)
|
126
159
|
attr_reader(attribute)
|
127
160
|
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Contains version information for the OmniAuth Identity gem.
|
3
4
|
module OmniAuth
|
4
5
|
module Identity
|
5
6
|
module Version
|
6
|
-
|
7
|
+
# @!attribute [r] VERSION
|
8
|
+
# The current version of the omniauth-identity gem.
|
9
|
+
# @return [String]
|
10
|
+
VERSION = "3.1.5"
|
7
11
|
end
|
8
12
|
end
|
9
13
|
end
|
data/lib/omniauth/identity.rb
CHANGED
@@ -18,19 +18,49 @@ end
|
|
18
18
|
|
19
19
|
require "omniauth"
|
20
20
|
|
21
|
+
# The main OmniAuth module.
|
21
22
|
module OmniAuth
|
23
|
+
# Container for OmniAuth strategy classes.
|
22
24
|
module Strategies
|
25
|
+
# Autoload the Identity strategy.
|
23
26
|
autoload :Identity, "omniauth/strategies/identity"
|
24
27
|
end
|
25
28
|
|
29
|
+
# OmniAuth Identity provides a way to authenticate users using a username and password
|
30
|
+
# stored in your application's database. It supports multiple ORMs and provides
|
31
|
+
# secure password hashing.
|
32
|
+
#
|
33
|
+
# @example Basic Setup
|
34
|
+
# # In your Gemfile
|
35
|
+
# gem 'omniauth-identity'
|
36
|
+
#
|
37
|
+
# # In your OmniAuth configuration
|
38
|
+
# use OmniAuth::Strategies::Identity,
|
39
|
+
# fields: [:email],
|
40
|
+
# model: User
|
41
|
+
#
|
42
|
+
# @see OmniAuth::Strategies::Identity
|
43
|
+
# @see OmniAuth::Identity::Model
|
44
|
+
# @see OmniAuth::Identity::SecurePassword
|
26
45
|
module Identity
|
46
|
+
# Autoload the Model module.
|
27
47
|
autoload :Model, "omniauth/identity/model"
|
48
|
+
# Autoload the SecurePassword module.
|
28
49
|
autoload :SecurePassword, "omniauth/identity/secure_password"
|
50
|
+
|
51
|
+
# Container for ORM-specific model adapters.
|
29
52
|
module Models
|
53
|
+
# Autoload the ActiveRecord adapter.
|
30
54
|
autoload :ActiveRecord, "omniauth/identity/models/active_record"
|
55
|
+
# Autoload the Mongoid adapter.
|
31
56
|
autoload :Mongoid, "omniauth/identity/models/mongoid"
|
57
|
+
# Autoload the CouchPotato adapter.
|
32
58
|
autoload :CouchPotatoModule, "omniauth/identity/models/couch_potato"
|
59
|
+
# Autoload the NoBrainer adapter.
|
33
60
|
autoload :NoBrainer, "omniauth/identity/models/nobrainer"
|
61
|
+
# Autoload the ROM adapter.
|
62
|
+
autoload :Rom, "omniauth/identity/models/rom"
|
63
|
+
# Autoload the Sequel adapter.
|
34
64
|
autoload :Sequel, "omniauth/identity/models/sequel"
|
35
65
|
end
|
36
66
|
end
|
@@ -5,27 +5,96 @@ module OmniAuth
|
|
5
5
|
# The identity strategy allows you to provide simple internal
|
6
6
|
# user authentication using the same process flow that you
|
7
7
|
# use for external OmniAuth providers.
|
8
|
+
#
|
9
|
+
# @example Basic Setup
|
10
|
+
# use OmniAuth::Strategies::Identity,
|
11
|
+
# fields: [:email],
|
12
|
+
# model: User
|
13
|
+
#
|
14
|
+
# @example With Registration
|
15
|
+
# use OmniAuth::Strategies::Identity,
|
16
|
+
# fields: [:email, :name],
|
17
|
+
# model: User,
|
18
|
+
# enable_registration: true
|
19
|
+
#
|
20
|
+
# @see https://github.com/omniauth/omniauth-identity
|
8
21
|
class Identity
|
22
|
+
# @!attribute [r] DEFAULT_REGISTRATION_FIELDS
|
23
|
+
# Default fields required for registration.
|
24
|
+
# @return [Array<Symbol>]
|
9
25
|
DEFAULT_REGISTRATION_FIELDS = %i[password password_confirmation].freeze
|
10
26
|
include OmniAuth::Strategy
|
27
|
+
|
28
|
+
# @!attribute [rw] fields
|
29
|
+
# The fields to collect for user registration.
|
30
|
+
# @return [Array<Symbol>]
|
11
31
|
option :fields, %i[name email]
|
12
32
|
|
13
|
-
#
|
33
|
+
# @!attribute [rw] enable_registration
|
34
|
+
# Whether to enable user registration functionality.
|
35
|
+
# @return [true, false]
|
14
36
|
option :enable_registration, true # See #other_phase and #request_phase
|
37
|
+
|
38
|
+
# @!attribute [rw] enable_login
|
39
|
+
# Whether to enable login functionality.
|
40
|
+
# @return [true, false]
|
15
41
|
option :enable_login, true # See #other_phase
|
16
42
|
|
17
|
-
#
|
43
|
+
# @!attribute [rw] on_login
|
44
|
+
# Custom login handler. If provided, called instead of showing the default login form.
|
45
|
+
# @return [Proc, nil]
|
18
46
|
option :on_login, nil # See #request_phase
|
47
|
+
|
48
|
+
# @!attribute [rw] on_validation
|
49
|
+
# Custom validation handler for registration.
|
50
|
+
# @return [Proc, nil]
|
19
51
|
option :on_validation, nil # See #registration_phase
|
52
|
+
|
53
|
+
# @!attribute [rw] on_registration
|
54
|
+
# Custom registration handler. If provided, called instead of showing the default registration form.
|
55
|
+
# @return [Proc, nil]
|
20
56
|
option :on_registration, nil # See #registration_phase
|
57
|
+
|
58
|
+
# @!attribute [rw] on_failed_registration
|
59
|
+
# Custom handler for failed registration.
|
60
|
+
# @return [Proc, nil]
|
21
61
|
option :on_failed_registration, nil # See #registration_phase
|
62
|
+
|
63
|
+
# @!attribute [rw] locate_conditions
|
64
|
+
# Conditions for locating an identity during login.
|
65
|
+
# @return [Proc, Hash]
|
22
66
|
option :locate_conditions, ->(req) { {model.auth_key => req.params["auth_key"]} }
|
67
|
+
|
68
|
+
# @!attribute [rw] create_identity_link_text
|
69
|
+
# Text for the link to create a new identity.
|
70
|
+
# @return [String]
|
23
71
|
option :create_identity_link_text, "Create an Identity"
|
72
|
+
|
73
|
+
# @!attribute [rw] registration_failure_message
|
74
|
+
# Message to display on registration failure.
|
75
|
+
# @return [String]
|
24
76
|
option :registration_failure_message, "One or more fields were invalid"
|
77
|
+
|
78
|
+
# @!attribute [rw] validation_failure_message
|
79
|
+
# Message to display on validation failure.
|
80
|
+
# @return [String]
|
25
81
|
option :validation_failure_message, "Validation failed"
|
82
|
+
|
83
|
+
# @!attribute [rw] title
|
84
|
+
# Title for the login form.
|
85
|
+
# @return [String]
|
26
86
|
option :title, "Identity Verification" # Title for Login Form
|
87
|
+
|
88
|
+
# @!attribute [rw] registration_form_title
|
89
|
+
# Title for the registration form.
|
90
|
+
# @return [String]
|
27
91
|
option :registration_form_title, "Register Identity" # Title for Registration Form
|
28
92
|
|
93
|
+
# Handles the initial request phase.
|
94
|
+
#
|
95
|
+
# Shows the login form or calls the custom on_login handler.
|
96
|
+
#
|
97
|
+
# @return [Rack::Response] the response to send
|
29
98
|
def request_phase
|
30
99
|
if options[:on_login]
|
31
100
|
options[:on_login].call(env)
|
@@ -34,12 +103,22 @@ module OmniAuth
|
|
34
103
|
end
|
35
104
|
end
|
36
105
|
|
106
|
+
# Handles the callback phase after login.
|
107
|
+
#
|
108
|
+
# Authenticates the user and calls super if successful.
|
109
|
+
#
|
110
|
+
# @return [void]
|
37
111
|
def callback_phase
|
38
112
|
return fail!(:invalid_credentials) unless identity
|
39
113
|
|
40
114
|
super
|
41
115
|
end
|
42
116
|
|
117
|
+
# Handles other phases like registration.
|
118
|
+
#
|
119
|
+
# Routes to registration or login based on the path and options.
|
120
|
+
#
|
121
|
+
# @return [void]
|
43
122
|
def other_phase
|
44
123
|
if options[:enable_registration] && on_registration_path?
|
45
124
|
if request.get?
|
@@ -61,6 +140,10 @@ module OmniAuth
|
|
61
140
|
end
|
62
141
|
end
|
63
142
|
|
143
|
+
# Shows the registration form or calls the custom on_registration handler.
|
144
|
+
#
|
145
|
+
# @param validation_message [String, nil] message to display if validation failed
|
146
|
+
# @return [Rack::Response] the response to send
|
64
147
|
def registration_form(validation_message = nil)
|
65
148
|
if options[:on_registration]
|
66
149
|
options[:on_registration].call(env)
|
@@ -69,6 +152,11 @@ module OmniAuth
|
|
69
152
|
end
|
70
153
|
end
|
71
154
|
|
155
|
+
# Handles the registration phase.
|
156
|
+
#
|
157
|
+
# Creates a new identity and saves it.
|
158
|
+
#
|
159
|
+
# @return [void]
|
72
160
|
def registration_phase
|
73
161
|
attributes = (options[:fields] + DEFAULT_REGISTRATION_FIELDS).each_with_object({}) do |k, h|
|
74
162
|
h[k] = request.params[k.to_s]
|
@@ -92,17 +180,33 @@ module OmniAuth
|
|
92
180
|
end
|
93
181
|
end
|
94
182
|
|
183
|
+
# @!method uid
|
184
|
+
# Returns the unique identifier for the authenticated identity.
|
185
|
+
# @return [String]
|
95
186
|
uid { identity.uid }
|
187
|
+
|
188
|
+
# @!method info
|
189
|
+
# Returns the info hash for the authenticated identity.
|
190
|
+
# @return [Hash]
|
96
191
|
info { identity.info }
|
97
192
|
|
193
|
+
# Returns the path for registration.
|
194
|
+
#
|
195
|
+
# @return [String] the registration path
|
98
196
|
def registration_path
|
99
197
|
options[:registration_path] || "#{script_name}#{path_prefix}/#{name}/register"
|
100
198
|
end
|
101
199
|
|
200
|
+
# Checks if the current request is for the registration path.
|
201
|
+
#
|
202
|
+
# @return [true, false]
|
102
203
|
def on_registration_path?
|
103
204
|
on_path?(registration_path)
|
104
205
|
end
|
105
206
|
|
207
|
+
# Finds and authenticates the identity based on the request parameters.
|
208
|
+
#
|
209
|
+
# @return [Object, nil] the authenticated identity or nil
|
106
210
|
def identity
|
107
211
|
conditions = options[:locate_conditions]
|
108
212
|
conditions = conditions.is_a?(Proc) ? instance_exec(request, &conditions).to_hash : conditions.to_hash
|
@@ -110,12 +214,18 @@ module OmniAuth
|
|
110
214
|
@identity ||= model.authenticate(conditions, request.params["password"])
|
111
215
|
end
|
112
216
|
|
217
|
+
# Returns the model class to use for identities.
|
218
|
+
#
|
219
|
+
# @return [Class] the identity model class
|
113
220
|
def model
|
114
221
|
options[:model] || ::Identity
|
115
222
|
end
|
116
223
|
|
117
224
|
private
|
118
225
|
|
226
|
+
# Builds the login form.
|
227
|
+
#
|
228
|
+
# @return [OmniAuth::Form] the login form
|
119
229
|
def build_omniauth_login_form
|
120
230
|
OmniAuth::Form.build(
|
121
231
|
title: options[:title],
|
@@ -129,6 +239,10 @@ module OmniAuth
|
|
129
239
|
end
|
130
240
|
end
|
131
241
|
|
242
|
+
# Builds the registration form.
|
243
|
+
#
|
244
|
+
# @param validation_message [String, nil] message to display
|
245
|
+
# @return [OmniAuth::Form] the registration form
|
132
246
|
def build_omniauth_registration_form(validation_message)
|
133
247
|
OmniAuth::Form.build(title: options[:registration_form_title]) do |f|
|
134
248
|
f.html("<p style='color:red'>#{validation_message}</p>") if validation_message
|
@@ -140,22 +254,26 @@ module OmniAuth
|
|
140
254
|
end
|
141
255
|
end
|
142
256
|
|
143
|
-
#
|
257
|
+
# Checks if validation is enabled.
|
144
258
|
#
|
145
|
-
# @return [
|
259
|
+
# @return [true, false]
|
146
260
|
def validating?
|
147
261
|
!!options[:on_validation]
|
148
262
|
end
|
149
263
|
|
150
|
-
# Validates the
|
264
|
+
# Validates the identity using the custom validation handler.
|
151
265
|
#
|
152
|
-
# @return [true
|
266
|
+
# @return [true, false] result of validation
|
153
267
|
def valid?
|
154
268
|
# on_validation may run a Captcha or other validation mechanism
|
155
269
|
# Must return true when validation passes, false otherwise
|
156
270
|
!!options[:on_validation].call(env: env)
|
157
271
|
end
|
158
272
|
|
273
|
+
# Handles registration failure.
|
274
|
+
#
|
275
|
+
# @param message [String] the failure message
|
276
|
+
# @return [void]
|
159
277
|
def registration_failure(message)
|
160
278
|
if options[:on_failed_registration]
|
161
279
|
options[:on_failed_registration].call(env)
|
@@ -164,6 +282,9 @@ module OmniAuth
|
|
164
282
|
end
|
165
283
|
end
|
166
284
|
|
285
|
+
# Handles the result of registration.
|
286
|
+
#
|
287
|
+
# @return [void]
|
167
288
|
def registration_result
|
168
289
|
if @identity.persisted?
|
169
290
|
env["PATH_INFO"] = "#{path_prefix}/#{name}/callback"
|
data.tar.gz.sig
CHANGED
Binary file
|