simple_authorize 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 705f75951bbd55e8352217a91088812ddb17a0914f5674c2e04c3ff5623d0f28
4
- data.tar.gz: 94af211cac4be86bac47e46d1f2e06ae6342d4d5fbc7bf0e41fe5e9f82591287
3
+ metadata.gz: 1e7ec7dc4e13ab781a1379671cb043b2a03956c29e32554ff00624ee57cd009a
4
+ data.tar.gz: d0fb3ae9757d4ccd93250a09847630e5f4958c32498bff21047cf1e903cc4c19
5
5
  SHA512:
6
- metadata.gz: 56b1be714196ca98cdced6ea2556e64c5086be5b4ed923eee1434cc939252fea1ac72fe905cb0b2e035db45075d3b91f373f139b64b32d78a3449d558f481374
7
- data.tar.gz: bd6f0bb6cf73dfcfbd8dde7e0be6a08d0073fa3f23ca7c18121a626c84c83692fc0f3bd2e1efb25131a132e22c188682eb30f99aa5370e05303104526eaecc0d
6
+ metadata.gz: ca8717f9564cdbe1fe7873e7c5c7f10452a8825d83170565f306272e9b5cfcbd18adf181081e3603c58e206eb743e6ee4b4b8d1a4fd477a11d5946a5bd793fea
7
+ data.tar.gz: cbe20818849f24ee61dec25ec75f08ba22c1bf75f5c9c2c1021cdaea3548aa01b084e009d16391c60568897148f13d2ba95e1c2817ae6c9697dea29fec9983a8
data/.overcommit.yml ADDED
@@ -0,0 +1,55 @@
1
+ # Overcommit configuration
2
+ # See https://github.com/sds/overcommit for full documentation
3
+
4
+ # Verify signatures for overcommit config to prevent tampering
5
+ verify_signatures: true
6
+
7
+ # Run these hooks before commits
8
+ PreCommit:
9
+ RuboCop:
10
+ enabled: true
11
+ description: 'Checking code style with RuboCop'
12
+ required: true
13
+ quiet: false
14
+ command: ['bundle', 'exec', 'rubocop']
15
+
16
+ TrailingWhitespace:
17
+ enabled: true
18
+ description: 'Checking for trailing whitespace'
19
+
20
+ YamlSyntax:
21
+ enabled: true
22
+ description: 'Checking YAML syntax'
23
+
24
+ # Run these hooks before pushes (more extensive checks)
25
+ PrePush:
26
+ RuboCop:
27
+ enabled: true
28
+ description: 'Running RuboCop before push'
29
+ required: true
30
+
31
+ Minitest:
32
+ enabled: true
33
+ description: 'Running Minitest suite'
34
+ required: true
35
+
36
+ RSpec:
37
+ enabled: true
38
+ description: 'Running RSpec suite'
39
+ required: true
40
+
41
+ # TruffleHog - scan for secrets
42
+ # Note: Requires truffleHog to be installed
43
+ # Install: brew install truffleHog (macOS) or pip install truffleHog
44
+ CustomScript:
45
+ enabled: true
46
+ description: 'Scanning for secrets with TruffleHog'
47
+ required: false # Optional since it requires external installation
48
+ command: ['sh', '-c', 'if command -v trufflehog >/dev/null 2>&1; then trufflehog filesystem . --only-verified --fail; else echo "TruffleHog not installed - skipping secret scan"; fi']
49
+
50
+ # Run these hooks after checkout
51
+ PostCheckout:
52
+ BundleInstall:
53
+ enabled: true
54
+ description: 'Running bundle install after checkout'
55
+ required: false
data/CHANGELOG.md CHANGED
@@ -5,49 +5,141 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [1.1.0] - 2025-11-05
9
9
 
10
10
  ### Added
11
- - Policy generator (`rails g simple_authorize:policy ModelName`) with support for:
12
- - Namespaced models (e.g., `Admin::Post`)
13
- - RSpec or Minitest test generation
14
- - Automatic test scaffolding with CRUD and scope tests
15
- - Policy caching for performance optimization:
16
- - Request-level memoization of policy instances
17
- - Automatic scoping by user, record, and policy class
18
- - Configurable via `config.enable_policy_cache`
19
- - `clear_policy_cache` method for manual cache clearing
20
- - Automatic cache clearing in `reset_authorization` for tests
21
- - Instrumentation and audit logging via ActiveSupport::Notifications:
22
- - Emits events for `authorize`, `authorize_headless`, and `policy_scope` calls
23
- - Rich payload with user, record, query, result, and timing information
24
- - Enabled by default, configurable via `config.enable_instrumentation`
25
- - Perfect for security audits, debugging, and monitoring
26
- - Initial release of SimpleAuthorize
27
- - Policy-based authorization system
28
- - Controller concern with authorization methods
29
- - Base policy class with default deny-all policies
30
- - Policy scope support for filtering collections
31
- - Strong parameters integration via `permitted_attributes` and `policy_params`
32
- - Automatic verification module (opt-in)
33
- - Headless policy support for policies without models
34
- - Namespace support for policies
35
- - Role-based helper methods (`admin_user?`, `contributor_user?`, `viewer_user?`)
36
- - Custom error handling with `NotAuthorizedError`
37
- - Install generator (`rails generate simple_authorize:install`)
38
- - Configuration system via initializer
39
- - Comprehensive documentation and examples
40
- - Test helper methods for easy testing
41
- - Backwards compatibility aliases for Pundit-style usage
42
-
43
- ## [0.1.0] - 2025-11-01
11
+
12
+ #### Policy Composition
13
+ - **Reusable Policy Modules** - Built-in modules for common authorization patterns
14
+ - **Ownable Module** - Ownership-based authorization helpers
15
+ - **Publishable Module** - Publishing workflow authorization
16
+ - **Timestamped Module** - Time-based access control
17
+ - **Approvable Module** - Approval workflow helpers
18
+ - **SoftDeletable Module** - Soft deletion authorization
19
+ - **Custom Module Support** - Easy creation of custom authorization modules
20
+
21
+ #### Context-Aware Policies
22
+ - **Request Context** - Pass additional context to policies (IP, time, location, etc.)
23
+ - **Controller Integration** - `authorization_context` method for building context
24
+ - **Context in Scopes** - Context available in Policy::Scope classes
25
+ - **Common Patterns** - Built-in support for geographic restrictions, time-based access, rate limiting
26
+
27
+ ## [1.0.0] - 2025-11-03
44
28
 
45
29
  ### Added
46
- - Initial gem structure
47
- - Core authorization framework extracted from production Rails application
48
- - MIT license
49
- - README with comprehensive documentation
50
- - Generator templates for installation
51
30
 
52
- [Unreleased]: https://github.com/scottlaplant/simple_authorize/compare/v0.1.0...HEAD
31
+ #### Core Authorization
32
+ - **Policy Generator** - Rails generator for creating policy classes (`rails g simple_authorize:policy ModelName`)
33
+ - **Install Generator** - Setup wizard creating initializer and base policy (`rails g simple_authorize:install`)
34
+ - **Headless Policies** - Authorization for actions without a specific record
35
+ - **Batch Authorization** - Efficiently authorize multiple records with `authorize_all`, `authorized_records`, and `partition_records`
36
+
37
+ #### Performance & Caching
38
+ - **Policy Caching** - Request-level memoization to reduce database queries and improve performance
39
+ - **Configurable Cache** - Enable/disable policy caching via `config.enable_policy_cache`
40
+
41
+ #### Instrumentation & Monitoring
42
+ - **ActiveSupport::Notifications** - Comprehensive instrumentation for authorization events
43
+ - **Audit Logging** - Track authorization attempts, denials, and policy scope usage
44
+ - **Custom Event Subscribers** - Hook into `authorize.simple_authorize` and `policy_scope.simple_authorize` events
45
+
46
+ #### API Support
47
+ - **JSON/XML Error Responses** - Automatic API-friendly error responses with proper HTTP status codes
48
+ - **API Request Detection** - Intelligent detection of API requests (JSON/XML format and headers)
49
+ - **Configurable Error Details** - Control error detail level with `config.api_error_details`
50
+ - **Status Code Handling** - 401 Unauthorized vs 403 Forbidden based on authentication state
51
+
52
+ #### Attribute-Level Authorization
53
+ - **Visible Attributes** - Control which attributes users can view (`visible_attributes`, `visible_attributes_for_action`)
54
+ - **Editable Attributes** - Control which attributes users can modify (`editable_attributes`, `editable_attributes_for_action`)
55
+ - **Filter Helpers** - Automatically filter attribute hashes based on policy rules
56
+ - **Strong Parameters Integration** - `policy_params` method for seamless Rails strong parameters integration
57
+
58
+ #### Testing Support
59
+ - **RSpec Matchers** - `permit_action`, `forbid_action`, `permit_mass_assignment`, `forbid_mass_assignment`
60
+ - **RSpec Helpers** - `permit_editing`, `forbid_editing`, `permit_viewing`, `forbid_viewing`
61
+ - **Minitest Helpers** - `assert_permit_action`, `assert_forbid_action` for Minitest users
62
+ - **Policy Testing** - Comprehensive test helpers for both testing frameworks
63
+
64
+ #### Internationalization
65
+ - **I18n Support** - Configurable error messages with internationalization support
66
+ - **Custom Translations** - Per-policy and per-action error message translations
67
+ - **Configurable Scope** - Customize I18n scope with `config.i18n_scope`
68
+ - **Fallback Messages** - Graceful fallback to default messages when translations are missing
69
+
70
+ #### Security & Best Practices
71
+ - **Authorization Verification** - `verify_authorized` and `verify_policy_scoped` to catch missing authorization
72
+ - **Skip Authorization** - Explicit `skip_authorization` and `skip_policy_scope` methods
73
+ - **Auto-Verify Module** - Optional automatic verification with `include SimpleAuthorize::Controller::AutoVerify`
74
+ - **Safe Redirects** - Security-conscious redirect handling preventing open redirect vulnerabilities
75
+
76
+ #### Developer Experience
77
+ - **Comprehensive Documentation** - Extensive README with examples and best practices
78
+ - **Error Messages** - Clear, actionable error messages for common mistakes
79
+ - **Helper Methods** - View helpers automatically included (`policy`, `policy_scope`, `authorized_user`)
80
+ - **Role Helpers** - Convenient `admin_user?`, `contributor_user?`, `viewer_user?` methods
81
+
82
+ ### Changed
83
+ - Improved error handling with detailed exception information
84
+ - Enhanced policy class resolution with namespace support
85
+ - Better cache key generation for policy instances
86
+
87
+ ### Fixed
88
+ - Policy scope resolution for collection classes
89
+ - Safe referrer path handling for redirects
90
+ - API request detection edge cases
91
+
92
+ ### Security
93
+ - Added protection against open redirect vulnerabilities in `safe_referrer_path`
94
+ - Implemented proper HTTP status codes (401 vs 403) for API errors
95
+ - Enhanced authorization verification to prevent bypass attempts
96
+
97
+ ## [0.1.0] - Initial Development
98
+
99
+ ### Added
100
+ - Basic policy-based authorization
101
+ - Core authorization methods (`authorize`, `policy`, `policy_scope`)
102
+ - Integration with Rails controllers
103
+ - Basic test helpers
104
+ - Initial documentation
105
+
106
+ ---
107
+
108
+ ## Upgrading
109
+
110
+ ### From 0.1.0 to 1.0.0
111
+
112
+ **Breaking Changes:**
113
+ None - v1.0.0 is fully backward compatible with 0.1.0.
114
+
115
+ **New Features:**
116
+ All features listed above are opt-in and won't affect existing implementations.
117
+
118
+ **Recommended Updates:**
119
+ 1. Run `rails g simple_authorize:install` to generate the configuration file
120
+ 2. Enable policy caching for better performance: `config.enable_policy_cache = true`
121
+ 3. Enable instrumentation for monitoring: `config.enable_instrumentation = true`
122
+ 4. Add RSpec matchers to your spec_helper: `require 'simple_authorize/rspec'`
123
+
124
+ **Configuration:**
125
+ ```ruby
126
+ # config/initializers/simple_authorize.rb
127
+ SimpleAuthorize.configure do |config|
128
+ config.enable_policy_cache = true # Enable request-level policy caching
129
+ config.enable_instrumentation = true # Enable ActiveSupport::Notifications
130
+ config.api_error_details = false # Exclude sensitive details in API errors
131
+ config.i18n_enabled = true # Enable I18n support
132
+ config.i18n_scope = "simple_authorize" # I18n translation scope
133
+ config.default_error_message = "You are not authorized to perform this action."
134
+ end
135
+ ```
136
+
137
+ ## Support
138
+
139
+ - **Documentation**: [README.md](README.md)
140
+ - **Issues**: [GitHub Issues](https://github.com/scottlaplant/simple_authorize/issues)
141
+ - **Security**: [SECURITY.md](SECURITY.md)
142
+ - **Contributing**: [CONTRIBUTING.md](CONTRIBUTING.md)
143
+
144
+ [1.0.0]: https://github.com/scottlaplant/simple_authorize/releases/tag/v1.0.0
53
145
  [0.1.0]: https://github.com/scottlaplant/simple_authorize/releases/tag/v0.1.0
@@ -0,0 +1,129 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or
31
+ advances of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official e-mail address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ **simpleauthorize@gmail.com**.
64
+
65
+ All complaints will be reviewed and investigated promptly and fairly.
66
+
67
+ All community leaders are obligated to respect the privacy and security of the
68
+ reporter of any incident.
69
+
70
+ ## Enforcement Guidelines
71
+
72
+ Community leaders will follow these Community Impact Guidelines in determining
73
+ the consequences for any action they deem in violation of this Code of Conduct:
74
+
75
+ ### 1. Correction
76
+
77
+ **Community Impact**: Use of inappropriate language or other behavior deemed
78
+ unprofessional or unwelcome in the community.
79
+
80
+ **Consequence**: A private, written warning from community leaders, providing
81
+ clarity around the nature of the violation and an explanation of why the
82
+ behavior was inappropriate. A public apology may be requested.
83
+
84
+ ### 2. Warning
85
+
86
+ **Community Impact**: A violation through a single incident or series
87
+ of actions.
88
+
89
+ **Consequence**: A warning with consequences for continued behavior. No
90
+ interaction with the people involved, including unsolicited interaction with
91
+ those enforcing the Code of Conduct, for a specified period of time. This
92
+ includes avoiding interactions in community spaces as well as external channels
93
+ like social media. Violating these terms may lead to a temporary or
94
+ permanent ban.
95
+
96
+ ### 3. Temporary Ban
97
+
98
+ **Community Impact**: A serious violation of community standards, including
99
+ sustained inappropriate behavior.
100
+
101
+ **Consequence**: A temporary ban from any sort of interaction or public
102
+ communication with the community for a specified period of time. No public or
103
+ private interaction with the people involved, including unsolicited interaction
104
+ with those enforcing the Code of Conduct, is allowed during this period.
105
+ Violating these terms may lead to a permanent ban.
106
+
107
+ ### 4. Permanent Ban
108
+
109
+ **Community Impact**: Demonstrating a pattern of violation of community
110
+ standards, including sustained inappropriate behavior, harassment of an
111
+ individual, or aggression toward or disparagement of classes of individuals.
112
+
113
+ **Consequence**: A permanent ban from any sort of public interaction within
114
+ the community.
115
+
116
+ ## Attribution
117
+
118
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119
+ version 2.0, available at
120
+ https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121
+
122
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct
123
+ enforcement ladder](https://github.com/mozilla/diversity).
124
+
125
+ [homepage]: https://www.contributor-covenant.org
126
+
127
+ For answers to common questions about this code of conduct, see the FAQ at
128
+ https://www.contributor-covenant.org/faq. Translations are available at
129
+ https://www.contributor-covenant.org/translations.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,182 @@
1
+ # Contributing to SimpleAuthorize
2
+
3
+ Thank you for your interest in contributing to SimpleAuthorize! We welcome contributions from everyone.
4
+
5
+ ## Code of Conduct
6
+
7
+ This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to simpleauthorize@gmail.com.
8
+
9
+ ## How Can I Contribute?
10
+
11
+ ### Reporting Bugs
12
+
13
+ Before creating bug reports, please check the existing issues to avoid duplicates. When creating a bug report, include as many details as possible:
14
+
15
+ * **Use a clear and descriptive title**
16
+ * **Describe the exact steps to reproduce the problem**
17
+ * **Provide specific examples** to demonstrate the steps
18
+ * **Describe the behavior you observed** and what you expected
19
+ * **Include Ruby version, Rails version, and gem version**
20
+ * **Include any error messages or stack traces**
21
+
22
+ ### Suggesting Enhancements
23
+
24
+ Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion:
25
+
26
+ * **Use a clear and descriptive title**
27
+ * **Provide a step-by-step description** of the suggested enhancement
28
+ * **Explain why this enhancement would be useful**
29
+ * **List any alternative solutions** you've considered
30
+
31
+ ### Pull Requests
32
+
33
+ * Fill in the pull request template
34
+ * Follow the Ruby style guide (RuboCop will check this)
35
+ * Include tests for new features or bug fixes
36
+ * Update documentation as needed
37
+ * Ensure all tests pass (`bundle exec rake test` and `bundle exec rspec`)
38
+ * Ensure RuboCop passes (`bundle exec rubocop`)
39
+
40
+ ## Development Setup
41
+
42
+ 1. **Fork and clone the repository**
43
+ ```bash
44
+ git clone https://github.com/YOUR_USERNAME/simple_authorize.git
45
+ cd simple_authorize
46
+ ```
47
+
48
+ 2. **Install dependencies**
49
+ ```bash
50
+ bundle install
51
+ ```
52
+
53
+ 3. **Set up git hooks with Overcommit**
54
+ ```bash
55
+ # Install git hooks
56
+ bundle exec overcommit --install
57
+
58
+ # (Optional) Install TruffleHog for secret scanning
59
+ # macOS: brew install truffleHog
60
+ # Linux: pip install truffleHog
61
+ ```
62
+
63
+ This sets up automatic checks before commits and pushes:
64
+ * **Pre-commit**: RuboCop, trailing whitespace, YAML syntax
65
+ * **Pre-push**: RuboCop, Minitest, RSpec, TruffleHog (if installed)
66
+ * **Post-checkout**: Automatic bundle install
67
+
68
+ To skip hooks temporarily (not recommended):
69
+ ```bash
70
+ git push --no-verify
71
+ ```
72
+
73
+ 4. **Run tests**
74
+ ```bash
75
+ # Run Minitest suite
76
+ bundle exec rake test
77
+
78
+ # Run RSpec suite
79
+ bundle exec rspec
80
+
81
+ # Run RuboCop
82
+ bundle exec rubocop
83
+
84
+ # Run all checks
85
+ bundle exec rake
86
+ ```
87
+
88
+ 5. **Create a feature branch**
89
+ ```bash
90
+ git checkout -b my-new-feature
91
+ ```
92
+
93
+ ## Testing
94
+
95
+ We maintain high test coverage (89%+) and use both Minitest and RSpec:
96
+
97
+ * **Minitest**: `test/` directory - for integration and controller tests
98
+ * **RSpec**: `spec/` directory - for unit tests and matchers
99
+
100
+ Please ensure your changes include appropriate tests:
101
+
102
+ ```ruby
103
+ # Minitest example
104
+ test "authorize succeeds when policy allows" do
105
+ result = controller.authorize(post, :show?)
106
+ assert_equal post, result
107
+ end
108
+
109
+ # RSpec example
110
+ it "permits action when policy allows" do
111
+ expect { policy.show? }.to permit_action
112
+ end
113
+ ```
114
+
115
+ ## Code Style
116
+
117
+ We follow the Ruby Style Guide and enforce it with RuboCop:
118
+
119
+ * Use 2 spaces for indentation
120
+ * Use double quotes for strings
121
+ * Keep lines under 120 characters
122
+ * Write descriptive method and variable names
123
+ * Add comments for complex logic
124
+
125
+ Run RuboCop with:
126
+ ```bash
127
+ bundle exec rubocop
128
+ ```
129
+
130
+ Auto-fix issues with:
131
+ ```bash
132
+ bundle exec rubocop -a
133
+ ```
134
+
135
+ ## Documentation
136
+
137
+ * Update README.md if you add features
138
+ * Add YARD documentation to public methods
139
+ * Update CHANGELOG.md with your changes
140
+ * Keep comments up-to-date with code changes
141
+
142
+ ## Commit Messages
143
+
144
+ * Use present tense ("Add feature" not "Added feature")
145
+ * Use imperative mood ("Move cursor to..." not "Moves cursor to...")
146
+ * Limit first line to 72 characters
147
+ * Reference issues and pull requests after the first line
148
+
149
+ Example:
150
+ ```
151
+ Add policy caching for improved performance
152
+
153
+ Implements request-level memoization for policy instances
154
+ to reduce database queries and improve response times.
155
+
156
+ Fixes #123
157
+ ```
158
+
159
+ ## Release Process
160
+
161
+ Maintainers will handle releases:
162
+
163
+ 1. Update version in `lib/simple_authorize/version.rb`
164
+ 2. Update CHANGELOG.md with release notes
165
+ 3. Commit changes
166
+ 4. Run `bundle exec rake release`
167
+
168
+ ## Questions?
169
+
170
+ Feel free to:
171
+ * Open an issue for questions
172
+ * Email us at simpleauthorize@gmail.com
173
+ * Check existing documentation in the README
174
+
175
+ ## Recognition
176
+
177
+ Contributors will be:
178
+ * Listed in the CHANGELOG for their contributions
179
+ * Credited in release notes
180
+ * Added to a CONTRIBUTORS file (if created)
181
+
182
+ Thank you for contributing to SimpleAuthorize! 🎉
data/README.md CHANGED
@@ -11,6 +11,8 @@ SimpleAuthorize is a lightweight, powerful authorization framework for Rails tha
11
11
  - **Policy-Based Authorization** - Define authorization rules in dedicated policy classes
12
12
  - **Scope Filtering** - Automatically filter collections based on user permissions
13
13
  - **Role-Based Access** - Built-in support for role-based authorization
14
+ - **Policy Composition** - Mix and match reusable authorization modules
15
+ - **Context-Aware Policies** - Make authorization decisions based on request context (IP, time, location, etc.)
14
16
  - **Zero Dependencies** - No external gems required (only Rails)
15
17
  - **Strong Parameters Integration** - Automatically build permitted params from policies
16
18
  - **Test Friendly** - Easy to test policies in isolation
@@ -394,6 +396,217 @@ SimpleAuthorize.configure do |config|
394
396
  end
395
397
  ```
396
398
 
399
+ ## Policy Composition
400
+
401
+ Policy Composition allows you to build complex authorization policies by combining reusable modules. This promotes DRY code and consistent authorization patterns across your application.
402
+
403
+ ### Using Built-in Policy Modules
404
+
405
+ SimpleAuthorize provides several ready-to-use policy modules:
406
+
407
+ ```ruby
408
+ class ArticlePolicy < ApplicationPolicy
409
+ include SimpleAuthorize::PolicyModules::Ownable
410
+ include SimpleAuthorize::PolicyModules::Publishable
411
+
412
+ def show?
413
+ published? || owner_or_admin?
414
+ end
415
+
416
+ def update?
417
+ owner_or_admin? && not_published?
418
+ end
419
+ end
420
+ ```
421
+
422
+ ### Available Policy Modules
423
+
424
+ #### Ownable
425
+ Provides ownership-based authorization:
426
+ - `owner?` - Check if user owns the record
427
+ - `owner_or_admin?` - Check if user is owner or admin
428
+ - `can_modify?` - Common pattern for modification rights
429
+
430
+ #### Publishable
431
+ For content with draft/published states:
432
+ - `published?` - Check if record is published
433
+ - `can_publish?` - Check if user can publish
434
+ - `can_preview?` - Check if user can preview drafts
435
+
436
+ #### Timestamped
437
+ Time-based authorization:
438
+ - `expired?` - Check if record has expired
439
+ - `within_time_window?` - Check if record is in valid time range
440
+ - `locked?` - Check if record is time-locked
441
+
442
+ #### Approvable
443
+ For approval workflows:
444
+ - `approved?` - Check if record is approved
445
+ - `can_approve?` - Check if user can approve (not their own content)
446
+ - `can_submit_for_approval?` - Check if user can submit for approval
447
+
448
+ #### SoftDeletable
449
+ For soft deletion support:
450
+ - `soft_deleted?` - Check if record is soft deleted
451
+ - `can_restore?` - Check if user can restore
452
+ - `can_permanently_destroy?` - Check if user can hard delete
453
+
454
+ ### Creating Custom Policy Modules
455
+
456
+ ```ruby
457
+ module MyApp::PolicyModules::Subscribable
458
+ protected
459
+
460
+ def subscriber?
461
+ user&.subscriptions&.active&.any?
462
+ end
463
+
464
+ def premium_subscriber?
465
+ user&.subscription&.premium?
466
+ end
467
+
468
+ def can_access_premium_content?
469
+ premium_subscriber? || admin?
470
+ end
471
+ end
472
+
473
+ class PremiumContentPolicy < ApplicationPolicy
474
+ include MyApp::PolicyModules::Subscribable
475
+
476
+ def show?
477
+ can_access_premium_content?
478
+ end
479
+ end
480
+ ```
481
+
482
+ ## Context-Aware Policies
483
+
484
+ Context-Aware Policies allow you to make authorization decisions based on additional context beyond just the user and record. This is useful for IP-based restrictions, time-based access, rate limiting, and more.
485
+
486
+ ### Basic Usage
487
+
488
+ Override the `authorization_context` method in your controller:
489
+
490
+ ```ruby
491
+ class ApplicationController < ActionController::Base
492
+ include SimpleAuthorize::Controller
493
+
494
+ private
495
+
496
+ def authorization_context
497
+ {
498
+ ip_address: request.remote_ip,
499
+ user_agent: request.user_agent,
500
+ current_time: Time.current,
501
+ country: request.location&.country,
502
+ two_factor_verified: session[:two_factor_verified],
503
+ user_plan: current_user&.subscription&.plan
504
+ }
505
+ end
506
+ end
507
+ ```
508
+
509
+ ### Using Context in Policies
510
+
511
+ Access context in your policies through the `context` method:
512
+
513
+ ```ruby
514
+ class SecureDocumentPolicy < ApplicationPolicy
515
+ def show?
516
+ # Require 2FA for sensitive documents
517
+ return false unless context[:two_factor_verified]
518
+
519
+ # Check IP restrictions
520
+ return false unless trusted_ip?
521
+
522
+ owner_or_admin?
523
+ end
524
+
525
+ private
526
+
527
+ def trusted_ip?
528
+ return true if context[:ip_address].nil?
529
+
530
+ trusted_ips = ["192.168.1.0/24", "10.0.0.0/8"]
531
+ trusted_ips.any? { |range| IPAddr.new(range).include?(context[:ip_address]) }
532
+ end
533
+ end
534
+ ```
535
+
536
+ ### Common Context Patterns
537
+
538
+ #### Geographic Restrictions
539
+ ```ruby
540
+ class RegionalContentPolicy < ApplicationPolicy
541
+ def show?
542
+ allowed_countries = ["US", "CA", "UK"]
543
+ allowed_countries.include?(context[:country]) || admin?
544
+ end
545
+ end
546
+ ```
547
+
548
+ #### Time-Based Access
549
+ ```ruby
550
+ class BusinessHoursPolicy < ApplicationPolicy
551
+ def create?
552
+ return true if admin?
553
+
554
+ hour = context[:current_time].hour
555
+ hour >= 9 && hour < 17 # 9 AM to 5 PM only
556
+ end
557
+ end
558
+ ```
559
+
560
+ #### Rate Limiting
561
+ ```ruby
562
+ class ApiPolicy < ApplicationPolicy
563
+ def create?
564
+ return true if admin?
565
+
566
+ request_count = context[:request_count] || 0
567
+ request_count < 100 # Limit to 100 requests
568
+ end
569
+ end
570
+ ```
571
+
572
+ #### Plan-Based Features
573
+ ```ruby
574
+ class ExportPolicy < ApplicationPolicy
575
+ def export?
576
+ case context[:user_plan]
577
+ when "enterprise"
578
+ true
579
+ when "pro"
580
+ owner_or_admin?
581
+ when "basic"
582
+ admin?
583
+ else
584
+ false
585
+ end
586
+ end
587
+ end
588
+ ```
589
+
590
+ ### Context with Policy Scopes
591
+
592
+ Context is also available in policy scopes:
593
+
594
+ ```ruby
595
+ class DocumentPolicy < ApplicationPolicy
596
+ class Scope < ApplicationPolicy::Scope
597
+ def resolve
598
+ if context[:department]
599
+ scope.where(department: context[:department])
600
+ elsif user.admin?
601
+ scope.all
602
+ else
603
+ scope.where(user: user)
604
+ end
605
+ end
606
+ end
607
+ end
608
+ ```
609
+
397
610
  ## Advanced Features
398
611
 
399
612
  ### Headless Policies
data/SECURITY.md CHANGED
@@ -6,7 +6,8 @@ We release patches for security vulnerabilities. Currently supported versions:
6
6
 
7
7
  | Version | Supported |
8
8
  | ------- | ------------------ |
9
- | 0.1.x | :white_check_mark: |
9
+ | 1.0.x | :white_check_mark: |
10
+ | < 1.0 | :x: |
10
11
 
11
12
  ## Reporting a Vulnerability
12
13
 
@@ -14,8 +15,11 @@ We take the security of SimpleAuthorize seriously. If you discover a security vu
14
15
 
15
16
  ### How to Report
16
17
 
17
- 1. **DO NOT** open a public GitHub issue for security vulnerabilities
18
- 2. Use GitHub's private vulnerability reporting feature (see "Security" tab in the repository)
18
+ **Please DO NOT open a public GitHub issue for security vulnerabilities.**
19
+
20
+ Please report security vulnerabilities to: **simpleauthorize@gmail.com**
21
+
22
+ Alternatively, you can use GitHub's private vulnerability reporting feature (see "Security" tab in the repository).
19
23
 
20
24
  ### What to Include
21
25
 
@@ -141,9 +141,9 @@ module SimpleAuthorize
141
141
  if SimpleAuthorize.configuration.enable_policy_cache
142
142
  policy_cache_key = build_policy_cache_key(record, policy_class)
143
143
  @_policy_cache ||= {}
144
- @_policy_cache[policy_cache_key] ||= policy_class.new(authorized_user, record)
144
+ @_policy_cache[policy_cache_key] ||= policy_class.new(authorized_user, record, context: authorization_context)
145
145
  else
146
- policy_class.new(authorized_user, record)
146
+ policy_class.new(authorized_user, record, context: authorization_context)
147
147
  end
148
148
  rescue NameError
149
149
  raise PolicyNotDefinedError, "unable to find policy `#{policy_class}` for `#{record}`"
@@ -163,7 +163,7 @@ module SimpleAuthorize
163
163
  error = nil
164
164
 
165
165
  begin
166
- result = policy_scope_class.new(authorized_user, scope).resolve
166
+ result = policy_scope_class.new(authorized_user, scope, context: authorization_context).resolve
167
167
  rescue NameError
168
168
  error = PolicyNotDefinedError.new("unable to find scope `#{policy_scope_class}` for `#{scope}`")
169
169
  end
@@ -277,6 +277,26 @@ module SimpleAuthorize
277
277
  current_user
278
278
  end
279
279
 
280
+ # Build context for authorization (can be overridden)
281
+ # Override this method in your ApplicationController to provide
282
+ # context data for policies
283
+ #
284
+ # Example:
285
+ # def authorization_context
286
+ # {
287
+ # ip_address: request.remote_ip,
288
+ # user_agent: request.user_agent,
289
+ # subdomain: request.subdomain,
290
+ # current_time: Time.current,
291
+ # request_count: rate_limiter.count_for(current_user),
292
+ # two_factor_verified: session[:two_factor_verified],
293
+ # user_plan: current_user&.subscription&.plan
294
+ # }
295
+ # end
296
+ def authorization_context
297
+ {}
298
+ end
299
+
280
300
  # Clear the policy cache
281
301
  def clear_policy_cache
282
302
  @_policy_cache = nil
@@ -5,9 +5,10 @@ module SimpleAuthorize
5
5
  class Policy
6
6
  attr_reader :user, :record
7
7
 
8
- def initialize(user, record)
8
+ def initialize(user, record, context: nil)
9
9
  @user = user
10
10
  @record = record
11
+ @context = context
11
12
  end
12
13
 
13
14
  # Default policies - deny everything by default
@@ -64,6 +65,10 @@ module SimpleAuthorize
64
65
  # Helper methods
65
66
  protected
66
67
 
68
+ def context
69
+ @context || {}
70
+ end
71
+
67
72
  def admin?
68
73
  user&.admin?
69
74
  end
@@ -96,9 +101,10 @@ module SimpleAuthorize
96
101
  class Scope
97
102
  attr_reader :user, :scope
98
103
 
99
- def initialize(user, scope)
104
+ def initialize(user, scope, context: nil)
100
105
  @user = user
101
106
  @scope = scope
107
+ @context = context
102
108
  end
103
109
 
104
110
  def resolve
@@ -107,6 +113,10 @@ module SimpleAuthorize
107
113
 
108
114
  protected
109
115
 
116
+ def context
117
+ @context || {}
118
+ end
119
+
110
120
  def admin?
111
121
  user&.admin?
112
122
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleAuthorize
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -5,6 +5,7 @@ require_relative "simple_authorize/version"
5
5
  require_relative "simple_authorize/configuration"
6
6
  require_relative "simple_authorize/controller"
7
7
  require_relative "simple_authorize/policy"
8
+ require_relative "simple_authorize/policy_modules"
8
9
  require_relative "simple_authorize/test_helpers"
9
10
 
10
11
  # SimpleAuthorize provides a lightweight authorization framework for Rails applications
data/spec/examples.txt CHANGED
@@ -1,51 +1,51 @@
1
1
  example_id | status | run_time |
2
2
  -------------------------------------- | ------ | --------------- |
3
- ./spec/rspec_matchers_spec.rb[1:1:1:1] | passed | 0.00004 seconds |
4
- ./spec/rspec_matchers_spec.rb[1:1:1:2] | passed | 0.00004 seconds |
3
+ ./spec/rspec_matchers_spec.rb[1:1:1:1] | passed | 0.00003 seconds |
4
+ ./spec/rspec_matchers_spec.rb[1:1:1:2] | passed | 0.00003 seconds |
5
5
  ./spec/rspec_matchers_spec.rb[1:1:1:3] | passed | 0.00004 seconds |
6
6
  ./spec/rspec_matchers_spec.rb[1:1:2:1] | passed | 0.00004 seconds |
7
7
  ./spec/rspec_matchers_spec.rb[1:1:2:2] | passed | 0.00004 seconds |
8
- ./spec/rspec_matchers_spec.rb[1:1:3:1] | passed | 0.00005 seconds |
8
+ ./spec/rspec_matchers_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
9
9
  ./spec/rspec_matchers_spec.rb[1:1:3:2] | passed | 0.00004 seconds |
10
- ./spec/rspec_matchers_spec.rb[1:1:4:1] | passed | 0.00004 seconds |
11
- ./spec/rspec_matchers_spec.rb[1:1:4:2] | passed | 0.00004 seconds |
12
- ./spec/rspec_matchers_spec.rb[1:2:1:1] | passed | 0.00004 seconds |
10
+ ./spec/rspec_matchers_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
11
+ ./spec/rspec_matchers_spec.rb[1:1:4:2] | passed | 0.00012 seconds |
12
+ ./spec/rspec_matchers_spec.rb[1:2:1:1] | passed | 0.00003 seconds |
13
13
  ./spec/rspec_matchers_spec.rb[1:2:1:2] | passed | 0.00004 seconds |
14
- ./spec/rspec_matchers_spec.rb[1:2:2:1] | passed | 0.00005 seconds |
15
- ./spec/rspec_matchers_spec.rb[1:2:2:2] | passed | 0.00011 seconds |
16
- ./spec/rspec_matchers_spec.rb[1:2:3:1] | passed | 0.00005 seconds |
17
- ./spec/rspec_matchers_spec.rb[1:2:3:2] | passed | 0.00007 seconds |
14
+ ./spec/rspec_matchers_spec.rb[1:2:2:1] | passed | 0.00004 seconds |
15
+ ./spec/rspec_matchers_spec.rb[1:2:2:2] | passed | 0.00003 seconds |
16
+ ./spec/rspec_matchers_spec.rb[1:2:3:1] | passed | 0.00004 seconds |
17
+ ./spec/rspec_matchers_spec.rb[1:2:3:2] | passed | 0.00004 seconds |
18
18
  ./spec/rspec_matchers_spec.rb[1:2:4:1] | passed | 0.00003 seconds |
19
- ./spec/rspec_matchers_spec.rb[1:2:4:2] | passed | 0.00004 seconds |
19
+ ./spec/rspec_matchers_spec.rb[1:2:4:2] | passed | 0.00003 seconds |
20
20
  ./spec/rspec_matchers_spec.rb[1:3:1:1] | passed | 0.00004 seconds |
21
- ./spec/rspec_matchers_spec.rb[1:3:1:2] | passed | 0.00004 seconds |
22
- ./spec/rspec_matchers_spec.rb[1:3:1:3] | passed | 0.00004 seconds |
21
+ ./spec/rspec_matchers_spec.rb[1:3:1:2] | passed | 0.00011 seconds |
22
+ ./spec/rspec_matchers_spec.rb[1:3:1:3] | passed | 0.00005 seconds |
23
23
  ./spec/rspec_matchers_spec.rb[1:3:2:1] | passed | 0.00004 seconds |
24
- ./spec/rspec_matchers_spec.rb[1:3:3:1] | passed | 0.00005 seconds |
25
- ./spec/rspec_matchers_spec.rb[1:3:3:2] | passed | 0.00004 seconds |
24
+ ./spec/rspec_matchers_spec.rb[1:3:3:1] | passed | 0.00004 seconds |
25
+ ./spec/rspec_matchers_spec.rb[1:3:3:2] | passed | 0.00005 seconds |
26
26
  ./spec/rspec_matchers_spec.rb[1:3:4:1] | passed | 0.00003 seconds |
27
- ./spec/rspec_matchers_spec.rb[1:3:4:2] | passed | 0.00004 seconds |
28
- ./spec/rspec_matchers_spec.rb[1:4:1:1] | passed | 0.00004 seconds |
29
- ./spec/rspec_matchers_spec.rb[1:4:2:1] | passed | 0.00004 seconds |
27
+ ./spec/rspec_matchers_spec.rb[1:3:4:2] | passed | 0.00003 seconds |
28
+ ./spec/rspec_matchers_spec.rb[1:4:1:1] | passed | 0.00003 seconds |
29
+ ./spec/rspec_matchers_spec.rb[1:4:2:1] | passed | 0.00003 seconds |
30
30
  ./spec/rspec_matchers_spec.rb[1:4:2:2] | passed | 0.00004 seconds |
31
- ./spec/rspec_matchers_spec.rb[1:4:3:1] | passed | 0.00005 seconds |
31
+ ./spec/rspec_matchers_spec.rb[1:4:3:1] | passed | 0.00004 seconds |
32
32
  ./spec/rspec_matchers_spec.rb[1:4:4:1] | passed | 0.00003 seconds |
33
- ./spec/rspec_matchers_spec.rb[1:4:4:2] | passed | 0.00004 seconds |
34
- ./spec/rspec_matchers_spec.rb[1:5:1:1] | passed | 0.00004 seconds |
35
- ./spec/rspec_matchers_spec.rb[1:5:1:2] | passed | 0.00004 seconds |
33
+ ./spec/rspec_matchers_spec.rb[1:4:4:2] | passed | 0.00003 seconds |
34
+ ./spec/rspec_matchers_spec.rb[1:5:1:1] | passed | 0.00003 seconds |
35
+ ./spec/rspec_matchers_spec.rb[1:5:1:2] | passed | 0.00003 seconds |
36
36
  ./spec/rspec_matchers_spec.rb[1:5:2:1] | passed | 0.00004 seconds |
37
- ./spec/rspec_matchers_spec.rb[1:5:3:1] | passed | 0.00005 seconds |
38
- ./spec/rspec_matchers_spec.rb[1:5:3:2] | passed | 0.00004 seconds |
39
- ./spec/rspec_matchers_spec.rb[1:5:4:1] | passed | 0.00004 seconds |
40
- ./spec/rspec_matchers_spec.rb[1:5:4:2] | passed | 0.00004 seconds |
41
- ./spec/rspec_matchers_spec.rb[1:6:1:1] | passed | 0.00004 seconds |
42
- ./spec/rspec_matchers_spec.rb[1:6:2:1] | passed | 0.00004 seconds |
37
+ ./spec/rspec_matchers_spec.rb[1:5:3:1] | passed | 0.00004 seconds |
38
+ ./spec/rspec_matchers_spec.rb[1:5:3:2] | passed | 0.00003 seconds |
39
+ ./spec/rspec_matchers_spec.rb[1:5:4:1] | passed | 0.00003 seconds |
40
+ ./spec/rspec_matchers_spec.rb[1:5:4:2] | passed | 0.00003 seconds |
41
+ ./spec/rspec_matchers_spec.rb[1:6:1:1] | passed | 0.00003 seconds |
42
+ ./spec/rspec_matchers_spec.rb[1:6:2:1] | passed | 0.00003 seconds |
43
43
  ./spec/rspec_matchers_spec.rb[1:6:2:2] | passed | 0.00004 seconds |
44
44
  ./spec/rspec_matchers_spec.rb[1:6:3:1] | passed | 0.00004 seconds |
45
- ./spec/rspec_matchers_spec.rb[1:6:3:2] | passed | 0.00005 seconds |
45
+ ./spec/rspec_matchers_spec.rb[1:6:3:2] | passed | 0.00004 seconds |
46
46
  ./spec/rspec_matchers_spec.rb[1:6:4:1] | passed | 0.00003 seconds |
47
47
  ./spec/rspec_matchers_spec.rb[1:6:4:2] | passed | 0.00003 seconds |
48
48
  ./spec/rspec_matchers_spec.rb[1:7:1:1] | passed | 0.00004 seconds |
49
- ./spec/rspec_matchers_spec.rb[1:7:1:2] | passed | 0.00005 seconds |
50
- ./spec/rspec_matchers_spec.rb[1:7:1:3] | passed | 0.00003 seconds |
51
- ./spec/rspec_matchers_spec.rb[1:7:2:1] | passed | 0.00156 seconds |
49
+ ./spec/rspec_matchers_spec.rb[1:7:1:2] | passed | 0.00004 seconds |
50
+ ./spec/rspec_matchers_spec.rb[1:7:1:3] | passed | 0.00005 seconds |
51
+ ./spec/rspec_matchers_spec.rb[1:7:2:1] | passed | 0.00118 seconds |
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Start SimpleCov before anything else
4
- require "simplecov"
5
- SimpleCov.command_name "RSpec"
6
-
7
3
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
8
4
  require "simple_authorize"
9
5
  require "simple_authorize/rspec"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_authorize
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-03 00:00:00.000000000 Z
11
+ date: 2025-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,31 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
- - !ruby/object:Gem::Dependency
84
- name: simplecov
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.22'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.22'
97
83
  description: SimpleAuthorize is a lightweight authorization framework for Rails that
98
84
  provides policy-based access control, role management, and scope filtering without
99
85
  requiring external gems. Inspired by Pundit but completely standalone.
100
86
  email:
101
- - scottlaplant@users.noreply.github.com
87
+ - simpleauthorize@gmail.com
102
88
  executables: []
103
89
  extensions: []
104
90
  extra_rdoc_files: []
105
91
  files:
92
+ - ".overcommit.yml"
106
93
  - ".simplecov"
107
94
  - CHANGELOG.md
95
+ - CODE_OF_CONDUCT.md
96
+ - CONTRIBUTING.md
108
97
  - LICENSE.txt
109
98
  - README.md
110
99
  - Rakefile
@@ -137,6 +126,7 @@ metadata:
137
126
  source_code_uri: https://github.com/scottlaplant/simple_authorize
138
127
  changelog_uri: https://github.com/scottlaplant/simple_authorize/blob/main/CHANGELOG.md
139
128
  bug_tracker_uri: https://github.com/scottlaplant/simple_authorize/issues
129
+ documentation_uri: https://github.com/scottlaplant/simple_authorize/wiki
140
130
  rubygems_mfa_required: 'true'
141
131
  post_install_message:
142
132
  rdoc_options: []