net-sasl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +23 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +198 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/net/sasl.rb +67 -0
- data/lib/net/sasl/authenticator.rb +82 -0
- data/lib/net/sasl/cram_md5_authenticator.rb +69 -0
- data/lib/net/sasl/digest_md5_authenticator.rb +149 -0
- data/lib/net/sasl/login_authenticator.rb +58 -0
- data/lib/net/sasl/plain_authenticator.rb +54 -0
- data/lib/net/sasl/registry.rb +52 -0
- data/lib/net/sasl/version.rb +7 -0
- data/net-sasl.gemspec +32 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 808eb424556d20a21234ed67c852cf7f664c3964b98109ff0180ab16aea19da1
|
4
|
+
data.tar.gz: 302b360c9e02f77d72d3f3daf9124444df70ee73c51d489a116b8eb87c1ab720
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c1747077cf7bbd3d16a248491b4143ef6e6e33e347a9c8c35025c803079b42077584ebf46df977103599d1d7d84b5d9c5f3a5bb0f5ab4d4e68278bc9fdc422d2
|
7
|
+
data.tar.gz: 453cc6578665f03b5f4dce9d9fcd039caee5e0f5238ec3fc499c566d5357b0ee8f5c2d5d0074e06597d81705585b5a2a65a7c8356856356f301617e6a601c6b4
|
@@ -0,0 +1,23 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
|
+
strategy:
|
9
|
+
matrix:
|
10
|
+
ruby: [ '3.0', 2.7, 2.6, 2.5, head ]
|
11
|
+
os: [ ubuntu-latest, macos-latest ]
|
12
|
+
runs-on: ${{ matrix.os }}
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
bundler-cache: true
|
20
|
+
- name: Install dependencies
|
21
|
+
run: bundle install
|
22
|
+
- name: Run the default task (tests, rubocop, etc)
|
23
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
inherit_mode:
|
2
|
+
merge:
|
3
|
+
- Exclude
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
TargetRubyVersion: 2.5
|
7
|
+
NewCops: disable
|
8
|
+
Exclude:
|
9
|
+
- bin/rake
|
10
|
+
- bin/rspec
|
11
|
+
- bin/rubocop
|
12
|
+
|
13
|
+
###########################################################################
|
14
|
+
# rubocop defaults are distractingly WRONG about many rules... Sorry. :(
|
15
|
+
|
16
|
+
###########################################################################
|
17
|
+
# Layout: Alignment. I want these to work, I really do...
|
18
|
+
|
19
|
+
# I wish this worked with "table". but that goes wrong sometimes.
|
20
|
+
Layout/HashAlignment: { Enabled: false }
|
21
|
+
|
22
|
+
# This needs to be configurable so parenthesis calls are aligned with first
|
23
|
+
# parameter, and non-parenthesis calls are aligned with fixed indentation.
|
24
|
+
Layout/ParameterAlignment: { Enabled: false }
|
25
|
+
|
26
|
+
###########################################################################
|
27
|
+
# Layout: Empty lines
|
28
|
+
|
29
|
+
Layout/EmptyLineAfterGuardClause: { Enabled: false }
|
30
|
+
Layout/EmptyLineAfterMagicComment: { Enabled: true }
|
31
|
+
Layout/EmptyLineAfterMultilineCondition: { Enabled: false }
|
32
|
+
Layout/EmptyLines: { Enabled: true }
|
33
|
+
Layout/EmptyLinesAroundAccessModifier: { Enabled: true }
|
34
|
+
Layout/EmptyLinesAroundArguments: { Enabled: true }
|
35
|
+
Layout/EmptyLinesAroundBeginBody: { Enabled: true }
|
36
|
+
Layout/EmptyLinesAroundBlockBody: { Enabled: false }
|
37
|
+
Layout/EmptyLinesAroundExceptionHandlingKeywords: { Enabled: true }
|
38
|
+
Layout/EmptyLinesAroundMethodBody: { Enabled: true }
|
39
|
+
|
40
|
+
Layout/EmptyLineBetweenDefs:
|
41
|
+
Enabled: true
|
42
|
+
AllowAdjacentOneLineDefs: true
|
43
|
+
|
44
|
+
Layout/EmptyLinesAroundAttributeAccessor:
|
45
|
+
inherit_mode:
|
46
|
+
merge:
|
47
|
+
- Exclude
|
48
|
+
- AllowedMethods
|
49
|
+
Enabled: true
|
50
|
+
AllowedMethods:
|
51
|
+
- delegate
|
52
|
+
- def_delegator
|
53
|
+
- def_delegators
|
54
|
+
- def_instance_delegators
|
55
|
+
|
56
|
+
# "empty_lines_special" sometimes does the wrong thing and annoys me.
|
57
|
+
# I'd almost learned to live with it... almost. 🙁
|
58
|
+
|
59
|
+
Layout/EmptyLinesAroundClassBody:
|
60
|
+
Enabled: false
|
61
|
+
EnforcedStyle: empty_lines_special
|
62
|
+
|
63
|
+
Layout/EmptyLinesAroundModuleBody:
|
64
|
+
Enabled: false
|
65
|
+
EnforcedStyle: empty_lines_special
|
66
|
+
|
67
|
+
###########################################################################
|
68
|
+
# Layout: Space around, before, inside, etc
|
69
|
+
|
70
|
+
Layout/SpaceAroundEqualsInParameterDefault: { Enabled: false }
|
71
|
+
Layout/SpaceBeforeBlockBraces: { Enabled: false }
|
72
|
+
Layout/SpaceBeforeFirstArg: { Enabled: false }
|
73
|
+
Layout/SpaceInLambdaLiteral: { Enabled: false }
|
74
|
+
Layout/SpaceInsideArrayLiteralBrackets: { Enabled: false }
|
75
|
+
Layout/SpaceInsideHashLiteralBraces: { Enabled: false }
|
76
|
+
|
77
|
+
Layout/SpaceInsideBlockBraces:
|
78
|
+
EnforcedStyle: space
|
79
|
+
EnforcedStyleForEmptyBraces: space
|
80
|
+
SpaceBeforeBlockParameters: false
|
81
|
+
|
82
|
+
# I would enable this if it were handled alignment better
|
83
|
+
Layout/ExtraSpacing:
|
84
|
+
Enabled: false
|
85
|
+
AllowForAlignment: true
|
86
|
+
AllowBeforeTrailingComments: true
|
87
|
+
|
88
|
+
###########################################################################
|
89
|
+
# Layout: Misc
|
90
|
+
|
91
|
+
Layout/LineLength:
|
92
|
+
Max: 90 # should stay under 80, but we'll allow a little wiggle-room
|
93
|
+
|
94
|
+
Layout/MultilineOperationIndentation: { Enabled: false }
|
95
|
+
|
96
|
+
Layout/MultilineMethodCallIndentation:
|
97
|
+
EnforcedStyle: indented
|
98
|
+
|
99
|
+
###########################################################################
|
100
|
+
# Lint and Naming: rubocop defaults are mostly good, but...
|
101
|
+
|
102
|
+
Lint/UnusedMethodArgument: { Enabled: false }
|
103
|
+
Naming/BinaryOperatorParameterName: { Enabled: false } # def /(denominator)
|
104
|
+
Naming/RescuedExceptionsVariableName: { Enabled: false }
|
105
|
+
|
106
|
+
###########################################################################
|
107
|
+
# Matrics:
|
108
|
+
|
109
|
+
Metrics/CyclomaticComplexity:
|
110
|
+
Max: 10
|
111
|
+
|
112
|
+
Metrics/BlockLength:
|
113
|
+
CountAsOne:
|
114
|
+
- array
|
115
|
+
- hash
|
116
|
+
- heredoc
|
117
|
+
|
118
|
+
Metrics/ClassLength:
|
119
|
+
Max: 200
|
120
|
+
CountAsOne:
|
121
|
+
- array
|
122
|
+
- hash
|
123
|
+
- heredoc
|
124
|
+
|
125
|
+
###########################################################################
|
126
|
+
# Style...
|
127
|
+
|
128
|
+
Style/AccessorGrouping: { Enabled: false }
|
129
|
+
Style/AsciiComments: { Enabled: false } # 👮 can't stop our 🎉🥳🎊🥳!
|
130
|
+
Style/ClassAndModuleChildren: { Enabled: false }
|
131
|
+
Style/EachWithObject: { Enabled: false }
|
132
|
+
Style/FormatStringToken: { Enabled: false }
|
133
|
+
Style/FloatDivision: { Enabled: false }
|
134
|
+
Style/GuardClause: { Enabled: false } # usually nice to do, but...
|
135
|
+
Style/IfUnlessModifier: { Enabled: false }
|
136
|
+
Style/IfWithSemicolon: { Enabled: false }
|
137
|
+
Style/Lambda: { Enabled: false }
|
138
|
+
Style/LineEndConcatenation: { Enabled: false }
|
139
|
+
Style/MixinGrouping: { Enabled: false }
|
140
|
+
Style/MultilineBlockChain: { Enabled: false }
|
141
|
+
Style/NumericPredicate: { Enabled: false } # usually nice to do, but...
|
142
|
+
Style/ParallelAssignment: { Enabled: false } # use occasionally/sparingly
|
143
|
+
Style/PerlBackrefs: { Enabled: false } # use occasionally/sparingly
|
144
|
+
Style/RescueStandardError: { Enabled: false }
|
145
|
+
Style/Semicolon: { Enabled: false }
|
146
|
+
Style/SingleLineMethods: { Enabled: false }
|
147
|
+
Style/StabbyLambdaParentheses: { Enabled: false }
|
148
|
+
Style/WhenThen : { Enabled: false }
|
149
|
+
|
150
|
+
# I require trailing commas elsewhere, but these are optional
|
151
|
+
Style/TrailingCommaInArguments: { Enabled: false }
|
152
|
+
|
153
|
+
# If rubocop had an option to only enforce this on constants and literals (e.g.
|
154
|
+
# strings, regexp, range), I'd agree.
|
155
|
+
#
|
156
|
+
# But if you are using it e.g. on method arguments of unknown type, in the same
|
157
|
+
# style that ruby uses it with grep, then you are doing exactly the right thing.
|
158
|
+
Style/CaseEquality: { Enabled: false }
|
159
|
+
|
160
|
+
# I'd enable if "require_parentheses_when_complex" considered unary '!' simple.
|
161
|
+
Style/TernaryParentheses:
|
162
|
+
EnforcedStyle: require_parentheses_when_complex
|
163
|
+
Enabled: false
|
164
|
+
|
165
|
+
Style/BlockDelimiters:
|
166
|
+
inherit_mode:
|
167
|
+
merge:
|
168
|
+
- Exclude
|
169
|
+
- ProceduralMethods
|
170
|
+
- IgnoredMethods
|
171
|
+
- FunctionalMethods
|
172
|
+
EnforcedStyle: semantic
|
173
|
+
AllowBracesOnProceduralOneLiners: true
|
174
|
+
IgnoredMethods:
|
175
|
+
- expect # rspec
|
176
|
+
- profile # ruby-prof
|
177
|
+
- ips # benchmark-ips
|
178
|
+
|
179
|
+
|
180
|
+
Style/FormatString:
|
181
|
+
EnforcedStyle: percent
|
182
|
+
|
183
|
+
Style/StringLiterals:
|
184
|
+
Enabled: true
|
185
|
+
EnforcedStyle: double_quotes
|
186
|
+
|
187
|
+
Style/StringLiteralsInInterpolation:
|
188
|
+
Enabled: true
|
189
|
+
EnforcedStyle: double_quotes
|
190
|
+
|
191
|
+
Style/TrailingCommaInHashLiteral:
|
192
|
+
EnforcedStyleForMultiline: consistent_comma
|
193
|
+
|
194
|
+
Style/TrailingCommaInArrayLiteral:
|
195
|
+
EnforcedStyleForMultiline: consistent_comma
|
196
|
+
|
197
|
+
Style/YodaCondition:
|
198
|
+
EnforcedStyle: forbid_for_equality_operators_only
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at nicholas.evans@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 nicholas a. evans
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Net::SASL
|
2
|
+
|
3
|
+
Pluggable mechanisms to support protocols which use SASL.
|
4
|
+
|
5
|
+
Originally written for Net::IMAP by Shugo Maeda, and extracted to this library
|
6
|
+
by Nicholas Evans.
|
7
|
+
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'net-sasl'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle install
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install net-sasl
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
TODO: Write usage instructions here
|
28
|
+
|
29
|
+
## Development
|
30
|
+
|
31
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
32
|
+
`rake test-unit` to run the tests. You can also run `bin/console` for an
|
33
|
+
interactive prompt that will allow you to experiment.
|
34
|
+
|
35
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
36
|
+
release a new version, update the version number in `version.rb`, and then run
|
37
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
38
|
+
git commits and the created tag, and push the `.gem` file to
|
39
|
+
[rubygems.org](https://rubygems.org).
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
Bug reports and pull requests are welcome on GitHub at
|
44
|
+
https://github.com/nevans/net-sasl. This project is intended to be a safe,
|
45
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
46
|
+
the [code of
|
47
|
+
conduct](https://github.com/nevans/net-sasl/blob/master/CODE_OF_CONDUCT.md).
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
The gem is available as open source under the terms of the [MIT
|
52
|
+
License](https://opensource.org/licenses/MIT).
|
53
|
+
|
54
|
+
## Code of Conduct
|
55
|
+
|
56
|
+
Everyone interacting in the Net::SASL project's codebases, issue trackers, chat
|
57
|
+
rooms and mailing lists is expected to follow the [code of
|
58
|
+
conduct](https://github.com/nevans/net-sasl/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "net/sasl"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/net/sasl.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "sasl/version"
|
4
|
+
require_relative "sasl/registry"
|
5
|
+
require_relative "sasl/authenticator"
|
6
|
+
require_relative "sasl/cram_md5_authenticator"
|
7
|
+
require_relative "sasl/digest_md5_authenticator"
|
8
|
+
require_relative "sasl/login_authenticator"
|
9
|
+
require_relative "sasl/plain_authenticator"
|
10
|
+
|
11
|
+
module Net
|
12
|
+
|
13
|
+
# Pluggable authentication mechanisms for protocols which support SASL (Simple
|
14
|
+
# Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and XMPP.
|
15
|
+
# SASL is described by RFC4422[https://tools.ietf.org/html/rfc4422]: "SASL is
|
16
|
+
# conceptually a framework that provides an abstraction layer between
|
17
|
+
# protocols and mechanisms as illustrated in the following diagram."
|
18
|
+
#
|
19
|
+
# SMTP LDAP XMPP Other protocols ...
|
20
|
+
# \ | | /
|
21
|
+
# \ | | /
|
22
|
+
# SASL abstraction layer
|
23
|
+
# / | | \
|
24
|
+
# / | | \
|
25
|
+
# EXTERNAL GSSAPI PLAIN Other mechanisms ...
|
26
|
+
#
|
27
|
+
# This library was originally implemented for Net::IMAP, and has been
|
28
|
+
# extracted from there.
|
29
|
+
module SASL
|
30
|
+
|
31
|
+
# Superclass of SASL errors.
|
32
|
+
class Error < StandardError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Error raised when data is in the incorrect format.
|
36
|
+
class DataFormatError < Error
|
37
|
+
end
|
38
|
+
|
39
|
+
# Error raised when a challnge from the server is non-parseable or the
|
40
|
+
# mechanism implementation is unable to respond
|
41
|
+
class ChallengeParseError < Error
|
42
|
+
end
|
43
|
+
|
44
|
+
# Adds an authenticator to the global registry, for use with
|
45
|
+
# Net::SASL.authenticator. See Net::SASL::Registry#add_authenticator.
|
46
|
+
def self.add_authenticator(mechanism, authenticator)
|
47
|
+
DEFAULT_REGISTRY.add_authenticator(mechanism, authenticator)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Builds an authenticator in its initial state, based on +mechanism+ name.
|
51
|
+
# Any additional arguments will be passed directly to the chosen
|
52
|
+
# authenticator's +#new+ method. See Net::SASL::Registry#authenticator.
|
53
|
+
def self.authenticator(mechanism, *args, **kwargs)
|
54
|
+
DEFAULT_REGISTRY.authenticator(mechanism, *args, **kwargs)
|
55
|
+
end
|
56
|
+
|
57
|
+
# The default global registry used by Net::SASL.authenticator
|
58
|
+
DEFAULT_REGISTRY = Registry.new
|
59
|
+
|
60
|
+
add_authenticator "PLAIN", PlainAuthenticator
|
61
|
+
add_authenticator "LOGIN", LoginAuthenticator
|
62
|
+
add_authenticator "DIGEST-MD5", DigestMD5Authenticator
|
63
|
+
add_authenticator "CRAM-MD5", CramMD5Authenticator
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# A base class to use for SASL authenticators.
|
8
|
+
class Authenticator
|
9
|
+
|
10
|
+
# Creates a new authenticator.
|
11
|
+
#
|
12
|
+
# Each specific mechanism determines how the arguments are interpreted—see
|
13
|
+
# each mechanisms' documentation for details. Whenever it's reasonable,
|
14
|
+
# mechanisms should support the standard positional and keyword arguments
|
15
|
+
# and ignore any irrelevant or unknown arguments.
|
16
|
+
#
|
17
|
+
# === Standard arguments
|
18
|
+
#
|
19
|
+
# * +authcid+: the authentication identity, the identity associated with
|
20
|
+
# the authentication credentials. This is usually a +username+.
|
21
|
+
# * +credentials+: the authentication credentials, e.g. a +password+ or a
|
22
|
+
# secret bearer token. Some mechanisms may not require an explicit
|
23
|
+
# +authcid+ if it is encoded inside the authentication credentials.
|
24
|
+
# * +authzid+: the authorization identity, an identity to act as or on
|
25
|
+
# behalf of. If this is is not given (or is left blank), the server
|
26
|
+
# will derive an authorization identity from the authentication
|
27
|
+
# credentials, usually the same as the authentication identity.
|
28
|
+
#
|
29
|
+
# The server is responsible for verifying the client's credentials and
|
30
|
+
# verifying that the identity it associates with the client's credentials
|
31
|
+
# (e.g., the authentication identity) is allowed to act as the
|
32
|
+
# authorization identity. The precise form(s) of identities and
|
33
|
+
# credentials may be dictated by the mechanism and by the server.
|
34
|
+
#
|
35
|
+
# === Standard options
|
36
|
+
#
|
37
|
+
# * +host+: the server hostname which is being connected to
|
38
|
+
# * +port+: the server port being connected to
|
39
|
+
# * +realm+: some mechanisms use "realms" or "domains" to segment
|
40
|
+
# authentication identities. This is protocol dependant and it might be
|
41
|
+
# the same as +host+.
|
42
|
+
#
|
43
|
+
def initialize(authcid = nil, credentials = nil, authzid = nil, **_options)
|
44
|
+
@username = authcid
|
45
|
+
@password = credentials
|
46
|
+
@authzid = authzid
|
47
|
+
end
|
48
|
+
|
49
|
+
# Does this mechanism support sending an initial response via SASL-IR?
|
50
|
+
def supports_initial_response?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Process a +challenge+ string from the server and return the response.
|
55
|
+
# This method should be sent an unencoded challenge and return an
|
56
|
+
# unencoded response. The client is responsible for receiving and decoding
|
57
|
+
# the challenge, according the the specification of the specific protocol,
|
58
|
+
# e.g. IMAP4 base64 encodes challenges and responses.
|
59
|
+
#
|
60
|
+
# A nil +challenge+ will be sent to get the initial responses, when
|
61
|
+
# that is supported by the mechanism (#supports_initial_response? returns
|
62
|
+
# true) and by the protocol.
|
63
|
+
#
|
64
|
+
# Calling #process when #done? returns true has undefined behavior: it may
|
65
|
+
# raise an excepion, return the previous response again, or raise an
|
66
|
+
# exception.
|
67
|
+
def process(challenge)
|
68
|
+
raise NotImplementedError, "implemented by SASL mechanism subclasses"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Has the authenticator finished? If so, then clients must not call
|
72
|
+
# #process again. This is so clients can know authentication is supposed
|
73
|
+
# to have been completed, without needing to call #process and handle an
|
74
|
+
# exception there.
|
75
|
+
def done?
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
|
5
|
+
module Net
|
6
|
+
|
7
|
+
module SASL
|
8
|
+
|
9
|
+
# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
|
10
|
+
# RFC2195[https://tools.ietf.org/html/rfc2195].
|
11
|
+
#
|
12
|
+
# == Deprecated
|
13
|
+
#
|
14
|
+
# +CRAM-MD5+ is obsolete. It is included for compatibility with existing
|
15
|
+
# servers.
|
16
|
+
# {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html]
|
17
|
+
# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead.
|
18
|
+
class CramMD5Authenticator < Authenticator
|
19
|
+
|
20
|
+
attr_reader :username, :password, :done
|
21
|
+
|
22
|
+
alias done? done
|
23
|
+
private :done
|
24
|
+
|
25
|
+
# Provide the +username+ and +password+ credentials for authentication.
|
26
|
+
#
|
27
|
+
# CRAM-MD5 doesn't support +authzid+, and an ArgumentError will be raised
|
28
|
+
# if a third positional parameter is passed.
|
29
|
+
#
|
30
|
+
# This should generally be instantiated via Net::SASL.authenticator.
|
31
|
+
def initialize(username, password, **_options)
|
32
|
+
super
|
33
|
+
@username = username
|
34
|
+
@password = password
|
35
|
+
@done = false
|
36
|
+
end
|
37
|
+
|
38
|
+
# responds to the server's challenge using the HMAC-MD5 algorithm.
|
39
|
+
def process(challenge)
|
40
|
+
digest = hmac_md5(challenge, password)
|
41
|
+
"#{username} #{digest}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
47
|
+
|
48
|
+
def hmac_md5(text, key)
|
49
|
+
if key.length > 64
|
50
|
+
key = Digest::MD5.digest(key)
|
51
|
+
end
|
52
|
+
|
53
|
+
k_ipad = key + "\0" * (64 - key.length)
|
54
|
+
k_opad = key + "\0" * (64 - key.length)
|
55
|
+
(0..63).each do |i|
|
56
|
+
k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
|
57
|
+
k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
|
58
|
+
end
|
59
|
+
|
60
|
+
digest = Digest::MD5.digest(k_ipad + text)
|
61
|
+
|
62
|
+
Digest::MD5.hexdigest(k_opad + digest)
|
63
|
+
end
|
64
|
+
|
65
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
require "strscan"
|
5
|
+
|
6
|
+
module Net
|
7
|
+
|
8
|
+
module SASL
|
9
|
+
|
10
|
+
# Authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
|
11
|
+
# in RFC2831(https://tools.ietf.org/html/rfc2831).
|
12
|
+
#
|
13
|
+
# == Deprecated
|
14
|
+
#
|
15
|
+
# "+DIGEST-MD5+" has been deprecated by
|
16
|
+
# {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on
|
17
|
+
# for security. It is included for compatibility with existing servers.
|
18
|
+
class DigestMD5Authenticator < Authenticator
|
19
|
+
|
20
|
+
STAGE_ONE = :stage_one
|
21
|
+
STAGE_TWO = :stage_two
|
22
|
+
private_constant :STAGE_ONE, :STAGE_TWO
|
23
|
+
|
24
|
+
attr_reader :username, :password, :authzid
|
25
|
+
|
26
|
+
# Provide the +username+ and +password+ credentials. An optional
|
27
|
+
# +authzid+ is defined as: "The "authorization ID" as per
|
28
|
+
# RFC2222[https://tools.ietf.org/html/rfc2222],
|
29
|
+
# encoded in UTF-8. optional. If present, and the
|
30
|
+
# authenticating user has sufficient privilege, and the server supports
|
31
|
+
# it, then after authentication the server will use this identity for
|
32
|
+
# making all accesses and access checks. If the client specifies it, and
|
33
|
+
# the server does not support it, then the response-value will be
|
34
|
+
# incorrect, and authentication will fail."
|
35
|
+
#
|
36
|
+
# This should generally be instantiated via Net::SASL.authenticator.
|
37
|
+
def initialize(username, password, authzid = nil, **_options)
|
38
|
+
super
|
39
|
+
@username, @password, @authzid = username, password, authzid
|
40
|
+
@nc, @stage = {}, STAGE_ONE
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
44
|
+
|
45
|
+
# responds to the server's DIGEST-MD5 challenges
|
46
|
+
def process(challenge)
|
47
|
+
case @stage
|
48
|
+
when STAGE_ONE
|
49
|
+
@stage = STAGE_TWO
|
50
|
+
sparams = {}
|
51
|
+
c = StringScanner.new(challenge)
|
52
|
+
while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
|
53
|
+
k, v = c[1], c[2]
|
54
|
+
if v =~ /^"(.*)"$/
|
55
|
+
v = $1
|
56
|
+
if v =~ /,/
|
57
|
+
v = v.split(",")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
sparams[k] = v
|
61
|
+
end
|
62
|
+
|
63
|
+
raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.empty?
|
64
|
+
unless sparams["qop"].include?("auth")
|
65
|
+
raise Error,
|
66
|
+
"Server does not support auth (qop = #{sparams["qop"].join(",")})"
|
67
|
+
end
|
68
|
+
|
69
|
+
response = {
|
70
|
+
nonce: sparams["nonce"],
|
71
|
+
username: @username,
|
72
|
+
realm: sparams["realm"],
|
73
|
+
cnonce: Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand,
|
74
|
+
Process.pid.to_s,]),
|
75
|
+
'digest-uri': "imap/#{sparams["realm"]}",
|
76
|
+
qop: "auth",
|
77
|
+
maxbuf: 65_535,
|
78
|
+
nc: "%08d" % nc(sparams["nonce"]),
|
79
|
+
charset: sparams["charset"],
|
80
|
+
}
|
81
|
+
|
82
|
+
response[:authzid] = @authzid unless @authzid.nil?
|
83
|
+
|
84
|
+
# now, the real thing
|
85
|
+
a0 = Digest::MD5.digest([ response.values_at(:username, :realm),
|
86
|
+
@password, ].join(":"))
|
87
|
+
|
88
|
+
a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":")
|
89
|
+
a1 << ":#{response[:authzid]}" unless response[:authzid].nil?
|
90
|
+
|
91
|
+
a2 = "AUTHENTICATE:#{response[:'digest-uri']}"
|
92
|
+
if response[:qop] && response[:qop] =~ (/^auth-(?:conf|int)$/)
|
93
|
+
a2 << ":00000000000000000000000000000000"
|
94
|
+
end
|
95
|
+
|
96
|
+
response[:response] = Digest::MD5.hexdigest(
|
97
|
+
[
|
98
|
+
Digest::MD5.hexdigest(a1),
|
99
|
+
response.values_at(:nonce, :nc, :cnonce, :qop),
|
100
|
+
Digest::MD5.hexdigest(a2),
|
101
|
+
].join(":")
|
102
|
+
)
|
103
|
+
|
104
|
+
response.keys.map {|key| qdval(key.to_s, response[key]) }.join(",")
|
105
|
+
when STAGE_TWO
|
106
|
+
@stage = nil
|
107
|
+
# if at the second stage, return an empty string
|
108
|
+
if challenge =~ /rspauth=/
|
109
|
+
""
|
110
|
+
else
|
111
|
+
raise ChallengeParseError, challenge
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise ChallengeParseError, challenge
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
119
|
+
|
120
|
+
# returns true after two challenge/response stages
|
121
|
+
def done?
|
122
|
+
@stage.nil?
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def nc(nonce)
|
128
|
+
@nc[nonce] = if @nc.key? nonce
|
129
|
+
@nc[nonce] + 1
|
130
|
+
else
|
131
|
+
1
|
132
|
+
end
|
133
|
+
@nc[nonce]
|
134
|
+
end
|
135
|
+
|
136
|
+
# some responses need quoting
|
137
|
+
def qdval(k, v) # rubocop:disable Naming/MethodParameterName
|
138
|
+
return if k.nil? || v.nil?
|
139
|
+
if %w[username authzid realm nonce cnonce digest-uri qop].include? k
|
140
|
+
v.gsub!(/([\\"])/, "\\\1")
|
141
|
+
'%s="%s"' % [k, v]
|
142
|
+
else
|
143
|
+
"%s=%s" % [k, v]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# Authenticator for the "+LOGIN+" SASL mechanism. The authentication
|
8
|
+
# credentials are transmitted in cleartext so this mechanism should only be
|
9
|
+
# used over an encrypted link.
|
10
|
+
#
|
11
|
+
# === Deprecated
|
12
|
+
#
|
13
|
+
# The {SASL mechanisms registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
14
|
+
# marks "LOGIN" as obsoleted by "PLAIN". It is included here for
|
15
|
+
# compatibility with existing servers. See
|
16
|
+
# draft-murchison-sasl-login[https://www.iana.org/go/draft-murchison-sasl-login]
|
17
|
+
# for both specification and deprecation.
|
18
|
+
class LoginAuthenticator < Authenticator
|
19
|
+
|
20
|
+
attr_reader :username, :password
|
21
|
+
|
22
|
+
# Provide the +username+ and +password+ credentials for authentication.
|
23
|
+
#
|
24
|
+
# LOGIN doesn't support +authzid+, and an ArgumentError will be raised if
|
25
|
+
# a third positional parameter is passed.
|
26
|
+
#
|
27
|
+
# This should generally be instantiated via Net::SASL.authenticator.
|
28
|
+
def initialize(username, password, **_options)
|
29
|
+
super
|
30
|
+
@state = STATE_USER
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns the SASL response for +LOGIN+
|
34
|
+
def process(data)
|
35
|
+
case @state
|
36
|
+
when STATE_USER
|
37
|
+
@state = STATE_PASSWORD
|
38
|
+
@username
|
39
|
+
when STATE_PASSWORD
|
40
|
+
@state = STATE_DONE
|
41
|
+
@password
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns true after sending the username and password.
|
46
|
+
def done?
|
47
|
+
@state == STATE_DONE
|
48
|
+
end
|
49
|
+
|
50
|
+
STATE_USER = :USER
|
51
|
+
STATE_PASSWORD = :PASSWORD
|
52
|
+
STATE_DONE = :DONE
|
53
|
+
|
54
|
+
private_constant :STATE_USER, :STATE_PASSWORD
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# Authenticator for the "+PLAIN+" SASL mechanism, specified in
|
8
|
+
# RFC4616[https://tools.ietf.org/html/rfc4616]. The authentication
|
9
|
+
# credentials are transmitted in cleartext, so this mechanism should only be
|
10
|
+
# used over an encrypted link.
|
11
|
+
class PlainAuthenticator < Authenticator
|
12
|
+
|
13
|
+
NULL = -"\0".b
|
14
|
+
private_constant :NULL
|
15
|
+
|
16
|
+
attr_reader :username, :password, :authzid, :done
|
17
|
+
|
18
|
+
alias done? done
|
19
|
+
private :done
|
20
|
+
|
21
|
+
# +username+ is the authentication identity, the identity whose +password+ is
|
22
|
+
# used. +username+ is referred to as +authcid+ by
|
23
|
+
# RFC4616[https://tools.ietf.org/html/rfc4616].
|
24
|
+
#
|
25
|
+
# +authzid+ is the authorization identity (identity to act as). It can
|
26
|
+
# usually be left blank. When +authzid+ is left blank (nil or empty string)
|
27
|
+
# the server will derive an identity from the credentials and use that as the
|
28
|
+
# authorization identity.
|
29
|
+
#
|
30
|
+
# This should generally be instantiated via Net::SASL.authenticator.
|
31
|
+
def initialize(username, password, authzid = nil, **_options)
|
32
|
+
raise ArgumentError, "username contains NULL" if username&.include?(NULL)
|
33
|
+
raise ArgumentError, "password contains NULL" if password&.include?(NULL)
|
34
|
+
raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
|
35
|
+
super
|
36
|
+
@done = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# +PLAIN+ does support SASL-IR
|
40
|
+
def supports_initial_response?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# returns the SASL response for +PLAIN+
|
45
|
+
def process(data)
|
46
|
+
@done = true
|
47
|
+
"#{@authzid}\0#{@username}\0#{@password}"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# Registry for SASL mechanisms. Common usage can use the default global
|
8
|
+
# registry, via Net::SASL#authenticator.
|
9
|
+
class Registry
|
10
|
+
|
11
|
+
# Creates a new registry, which matches enabled SASL mechanisms with their
|
12
|
+
# implementations.
|
13
|
+
def initialize
|
14
|
+
@authenticators = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Adds an authenticator class for use with #authenticator. +mechanism+ is
|
18
|
+
# the {SASL mechanism name}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
19
|
+
# supported by +authenticator+ (for instance, "+PLAIN+"). The
|
20
|
+
# +authenticator+ is an class which defines a +#process+ method to handle
|
21
|
+
# authentication with the server. See e.g. Net::SASL::PlainAuthenticator.
|
22
|
+
#
|
23
|
+
# If +mechanism+ refers to an existing authenticator, it will be replaced
|
24
|
+
# by the new one.
|
25
|
+
def add_authenticator(mechanism, authenticator)
|
26
|
+
@authenticators[mechanism.upcase] = authenticator
|
27
|
+
end
|
28
|
+
|
29
|
+
# Deletes an authenticator from the registry. This can be useful to
|
30
|
+
# implement a policy that prohibits the use of default mechanisms.
|
31
|
+
def remove_authenticator(mechanism)
|
32
|
+
@authenticators.delete(mechanism.upcase)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Builds an authenticator in its initial state. +mechanism+ is the SASL
|
36
|
+
# mechanism name. All other arguments represent the credentials and other
|
37
|
+
# parameters or configuration, which will be passed directly to the chosen
|
38
|
+
# authenticator's +#new+ method. See Authenticator.new.
|
39
|
+
def authenticator(mechanism, authcid=nil, credentials=nil, authzid=nil, **kwargs)
|
40
|
+
mechanism = mechanism.upcase
|
41
|
+
unless @authenticators.key?(mechanism)
|
42
|
+
raise ArgumentError, 'unknown SASL mechanism - "%s"' % mechanism
|
43
|
+
end
|
44
|
+
@authenticators.fetch(mechanism)
|
45
|
+
.new(authcid, credentials, authzid, **kwargs)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/net-sasl.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/net/sasl/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "net-sasl"
|
7
|
+
spec.version = Net::SASL::VERSION
|
8
|
+
spec.authors = ["nicholas a. evans", "Shugo Maeda"]
|
9
|
+
spec.email = ["nicholas.evans@gmail.com", "shugo@ruby-lang.org"]
|
10
|
+
|
11
|
+
spec.summary = "Pluggable SASL mechanisms"
|
12
|
+
spec.description = "Pluggable mechanisms to support protocols which use SASL"
|
13
|
+
spec.homepage = "https://github.com/nevans/net-sasl"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) {
|
24
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
}
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) {|f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "digest"
|
31
|
+
spec.add_dependency "strscan"
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: net-sasl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nicholas a. evans
|
8
|
+
- Shugo Maeda
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-05-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: digest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: strscan
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: Pluggable mechanisms to support protocols which use SASL
|
43
|
+
email:
|
44
|
+
- nicholas.evans@gmail.com
|
45
|
+
- shugo@ruby-lang.org
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".github/workflows/main.yml"
|
51
|
+
- ".gitignore"
|
52
|
+
- ".rubocop.yml"
|
53
|
+
- CHANGELOG.md
|
54
|
+
- CODE_OF_CONDUCT.md
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/console
|
60
|
+
- bin/setup
|
61
|
+
- lib/net/sasl.rb
|
62
|
+
- lib/net/sasl/authenticator.rb
|
63
|
+
- lib/net/sasl/cram_md5_authenticator.rb
|
64
|
+
- lib/net/sasl/digest_md5_authenticator.rb
|
65
|
+
- lib/net/sasl/login_authenticator.rb
|
66
|
+
- lib/net/sasl/plain_authenticator.rb
|
67
|
+
- lib/net/sasl/registry.rb
|
68
|
+
- lib/net/sasl/version.rb
|
69
|
+
- net-sasl.gemspec
|
70
|
+
homepage: https://github.com/nevans/net-sasl
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata:
|
74
|
+
homepage_uri: https://github.com/nevans/net-sasl
|
75
|
+
source_code_uri: https://github.com/nevans/net-sasl
|
76
|
+
changelog_uri: https://github.com/nevans/net-sasl/blob/main/CHANGELOG.md
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.5.0
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.1.6
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Pluggable SASL mechanisms
|
96
|
+
test_files: []
|