Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
source 'https://rubygems.org'

gem 'addressable'
gem 'allowy', '>= 2.1.0'
gem 'bootsnap', require: false
gem 'clockwork', require: false
gem 'cloudfront-signer'
Expand Down
4 changes: 0 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ GEM
aliyun-sdk (0.8.0)
nokogiri (~> 1.6)
rest-client (~> 2.0)
allowy (2.1.0)
activesupport (>= 3.2)
i18n
ast (2.4.3)
azure-core (0.1.15)
faraday (~> 0.9)
Expand Down Expand Up @@ -619,7 +616,6 @@ DEPENDENCIES
actionview (~> 8.1.1)
activemodel (~> 8.1.2)
addressable
allowy (>= 2.1.0)
azure-storage-blob!
bootsnap
byebug
Expand Down
21 changes: 21 additions & 0 deletions lib/allowy/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2014 Dmytrii Nagirniak

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
41 changes: 41 additions & 0 deletions lib/allowy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Allowy (Internalized Copy)

This directory contains an internalized copy of the archived allowy authorization library:
https://github.com/dnagir/allowy

**License:** MIT License
**Copyright:** (c) 2014 Dmytrii Nagirniak
**Inlined version:** 2.1.0
**Source commit:** `5d2c6f09a9617a2ad097a3b11ecabb32d48ff80b` (2015-01-06)
**Upstream status:** Archived (last commit: 2015-01-06)

The upstream LICENSE file is included in this directory.

## Why Inlined

- The upstream repository was archived with no updates since 2015
- Removes external gem dependency
- CCNG only uses a subset of allowy functionality (AccessControl, Context, Registry)

## Changes from Upstream

**Files included:** `access_control.rb`, `context.rb`, `registry.rb` (with RuboCop fixes applied)

**Files skipped (not used by CCNG):**
- `controller_extensions.rb` - Rails helper_method integration
- `matchers.rb` and `rspec.rb` - RSpec `be_able_to` matcher (CCNG uses its own `allow_op_on_object`)
- `version.rb` - version constant

## Usage in CCNG

Allowy is used **only by the V2 API** for authorization. This code can be removed together with the V2 API removal.

Note: If `/v2/info` endpoint is kept after V2 removal, `InfoController` should be refactored to not extend `RestController::BaseController` first.

The V3 API uses a different authorization system (`VCAP::CloudController::Permissions`).

## Tests

```bash
bundle exec rspec spec/unit/lib/allowy/
```
67 changes: 67 additions & 0 deletions lib/allowy/access_control.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

# Inlined from https://github.com/dnagir/allowy
# See lib/allowy/README.md for details

module Allowy
# This module provides the interface for implementing the access control actions.
# In order to use it, mix it into a plain Ruby class and define methods ending with `?`.
#
# @example
# class PageAccess
# include Allowy::AccessControl
#
# def view?(page)
# page and page.wiki? and context.user_signed_in?
# end
# end
#
# And then you can check the permissions from a controller:
#
# @example
# def show
# @page = Page.find params[:id]
# authorize! :view, @page
# end
#
module AccessControl
extend ActiveSupport::Concern

included do
attr_reader :context
end

def initialize(ctx)
@context = ctx
end

def can?(action, subject, *params)
allowing, _payload = check_permission(action, subject, *params)
allowing
end

def cannot?(*)
!can?(*)
end

def authorize!(action, subject, *params)
allowing, payload = check_permission(action, subject, *params)
raise AccessDenied.new('Not authorized', action, subject, payload) unless allowing
end

def deny!(payload)
throw(:deny, payload)
end

private

def check_permission(action, subject, *params)
m = "#{action}?"
raise UndefinedAction.new("The #{self.class.name} needs to have #{m} method. Please define it.") unless respond_to?(m)

allowing = false
payload = catch(:deny) { allowing = send(m, subject, *params) }
[allowing, payload]
end
end
end
29 changes: 29 additions & 0 deletions lib/allowy/allowy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

# Inlined from https://github.com/dnagir/allowy
# See lib/allowy/README.md for details

require 'active_support'
require 'active_support/core_ext'
require 'active_support/concern'
require 'active_support/inflector'

require 'allowy/access_control'
require 'allowy/registry'
require 'allowy/context'

module Allowy
class UndefinedAccessControl < StandardError; end
class UndefinedAction < StandardError; end

class AccessDenied < StandardError
attr_reader :action, :subject, :payload

def initialize(message, action, subject, payload=nil)
super(message)
@action = action
@subject = subject
@payload = payload
end
end
end
49 changes: 49 additions & 0 deletions lib/allowy/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

# Inlined from https://github.com/dnagir/allowy
# See lib/allowy/README.md for details

module Allowy
# This module provides the default and common context for checking the permissions.
# It is mixed into controllers and provides an easy way to reuse it
# in other parts of the application (RSpec, Cucumber or standalone).
#
# @example
# class MyContext
# include Allowy::Context
# attr_accessor :current_user
#
# def initialize(user)
# @current_user = user
# end
# end
#
# And then you can easily check the permissions like so:
#
# @example
# MyContext.new(that_user).can?(:create, Blog)
#
module Context
extend ActiveSupport::Concern

def allowy_context
self
end

def current_allowy
@current_allowy ||= ::Allowy::Registry.new(allowy_context)
end

def can?(action, subject, *)
current_allowy.access_control_for!(subject).can?(action, subject, *)
end

def cannot?(action, subject, *)
current_allowy.access_control_for!(subject).cannot?(action, subject, *)
end

def authorize!(action, subject, *)
current_allowy.access_control_for!(subject).authorize!(action, subject, *)
end
end
end
50 changes: 50 additions & 0 deletions lib/allowy/registry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

# Inlined from https://github.com/dnagir/allowy
# See lib/allowy/README.md for details

module Allowy
# Registry maps objects to their corresponding Access classes.
# Given a Space object, it finds SpaceAccess class automatically.
class Registry
def initialize(ctx, options={})
options.assert_valid_keys(:access_suffix)
@context = ctx
@registry = {}
@options = options
end

def access_control_for!(subject)
ac = access_control_for(subject)
raise UndefinedAccessControl.new("Please define Access Control class for #{subject.inspect}") unless ac

ac
end

def access_control_for(subject)
# Try subject as decorated object
clazz = class_for(subject.class.source_class.name) if subject.class.respond_to?(:source_class)

# Try subject as an object
clazz ||= class_for(subject.class.name)

# Try subject as a class
clazz = class_for(subject.name) if !clazz && subject.is_a?(Class)

return unless clazz

# create a new instance or return existing
@registry[clazz] ||= clazz.new(@context)
end

private

def class_for(name)
"#{name}#{access_suffix}".safe_constantize
end

def access_suffix
@options.fetch(:access_suffix, 'Access')
end
end
end
2 changes: 1 addition & 1 deletion lib/cloud_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'oj'
require 'delayed_job'

require 'allowy'
require 'allowy/allowy'

require 'uaa/token_coder'

Expand Down
1 change: 0 additions & 1 deletion spec/spec_helper_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def self.init
require 'pry'

require 'cloud_controller'
require 'allowy/rspec'

require 'rspec_api_documentation'
require 'services'
Expand Down
83 changes: 83 additions & 0 deletions spec/unit/lib/allowy/access_control_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

require_relative 'allowy_spec_helper'

module Allowy
RSpec.describe 'checking permissions' do
let(:access) { SampleAccess.new(123) }

describe '#context as an arbitrary object' do
subject { access.context }

it 'returns the context passed to initialize' do
expect(subject.to_s).to eq('123')
end

it 'context is not zero' do
expect(subject.zero?).to be(false)
end

it 'can access the context in permission check' do
expect(access.can?(:context_is_123, nil)).to be(true)
end
end

describe '#can?' do
it 'returns true when permission allows' do
expect(access.can?(:read, 'allow')).to be(true)
end

it 'returns false when permission denies' do
expect(access.can?(:read, 'deny')).to be(false)
end
end

describe '#cannot?' do
it 'returns false when permission allows' do
expect(access.cannot?(:read, 'allow')).to be(false)
end

it 'returns true when permission denies' do
expect(access.cannot?(:read, 'deny')).to be(true)
end
end

it 'passes extra parameters' do
expect(access.can?(:extra_params, 'same', bar: 'same')).to be(true)
end

it 'denies with early termination' do
expect(access.can?(:early_deny, 'foo')).to be(false)
expect(access.can?(:early_deny, 'xx')).to be(false)
end

it 'raises if no permission defined' do
expect { access.can?(:write, 'allow') }.to raise_error(UndefinedAction) do |err|
expect(err.message).to include('write?')
end
end

describe '#authorize!' do
it 'raises AccessDenied when not authorized' do
expect { access.authorize!(:read, 'deny') }.to raise_error(AccessDenied) do |err|
expect(err.message).not_to be_empty
expect(err.action).to eq(:read)
expect(err.subject).to eq('deny')
end
end

it 'does not raise when authorized' do
expect { access.authorize!(:read, 'allow') }.not_to raise_error
end

it 'raises with payload on early termination' do
expect { access.authorize!(:early_deny, 'subject') }.to raise_error(AccessDenied) do |err|
expect(err.message).not_to be_empty
expect(err.action).to eq(:early_deny)
expect(err.subject).to eq('subject')
expect(err.payload).to eq('early terminate: subject')
end
end
end
end
end
Loading
Loading