Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Scopes value object to encapsulate scope operations #829

Merged
merged 1 commit into from
Jan 27, 2021
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: 1 addition & 0 deletions lib/shopify_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module ShopifyAPI
require 'shopify_api/countable'
require 'shopify_api/resources'
require 'shopify_api/session'
require 'shopify_api/api_access'
require 'shopify_api/message_enricher'
require 'shopify_api/connection'
require 'shopify_api/pagination_link_headers'
Expand Down
57 changes: 57 additions & 0 deletions lib/shopify_api/api_access.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module ShopifyAPI
class ApiAccess
SCOPE_DELIMITER = ','

def initialize(scope_names)
if scope_names.is_a?(String)
scope_names = scope_names.to_s.split(SCOPE_DELIMITER)
end

store_scopes(scope_names)
end

def covers?(scopes)
scopes.compressed_scopes <= expanded_scopes
end

def to_s
to_a.join(SCOPE_DELIMITER)
end

def to_a
compressed_scopes.to_a
end

def ==(other)
other.class == self.class &&
compressed_scopes == other.compressed_scopes
end

alias :eql? :==

def hash
compressed_scopes.hash
end

protected

attr_reader :compressed_scopes, :expanded_scopes
NabeelAhsen marked this conversation as resolved.
Show resolved Hide resolved

private

def store_scopes(scope_names)
scopes = scope_names.map(&:strip).reject(&:empty?).to_set
implied_scopes = scopes.map { |scope| implied_scope(scope) }.compact

@compressed_scopes = scopes - implied_scopes
@expanded_scopes = scopes + implied_scopes
end

def implied_scope(scope)
is_write_scope = scope =~ /\A(unauthenticated_)?write_(.*)\z/
"#{$1}read_#{$2}" if is_write_scope
end
end
end
2 changes: 1 addition & 1 deletion lib/shopify_api/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def initialize(domain:, token:, api_version: ShopifyAPI::Base.api_version, extra
end

def create_permission_url(scope, redirect_uri, options = {})
params = { client_id: api_key, scope: scope.join(','), redirect_uri: redirect_uri }
params = { client_id: api_key, scope: ShopifyAPI::ApiAccess.new(scope).to_s, redirect_uri: redirect_uri }
params[:state] = options[:state] if options[:state]
construct_oauth_url("authorize", params)
end
Expand Down
153 changes: 153 additions & 0 deletions test/api_access_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true
require 'test_helper'

class ApiAccessTest < Minitest::Test
def test_write_is_the_same_access_as_read_write_on_the_same_resource
read_write_orders = ShopifyAPI::ApiAccess.new(%w(read_orders write_orders))
write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))

assert_equal write_orders, read_write_orders
end

def test_write_is_the_same_access_as_read_write_on_the_same_unauthenticated_resource
unauthenticated_read_write_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_read_orders unauthenticated_write_orders))
unauthenticated_write_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_write_orders))

assert_equal unauthenticated_write_orders, unauthenticated_read_write_orders
end

def test_read_is_not_the_same_as_read_write_on_the_same_resource
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
read_write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders read_orders))

refute_equal read_write_orders, read_orders
end

def test_two_different_resources_are_not_equal
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
read_products = ShopifyAPI::ApiAccess.new(%w(read_products))

refute_equal read_orders, read_products
end

def test_two_identical_scopes_are_equal
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
read_orders_identical = ShopifyAPI::ApiAccess.new(%w(read_orders))

assert_equal read_orders_identical, read_orders
end

def test_unauthenticated_is_not_implied_by_authenticated_access
unauthenticated_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_read_orders))
authenticated_read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
authenticated_write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))

refute_equal unauthenticated_orders, authenticated_read_orders
refute_equal unauthenticated_orders, authenticated_write_orders
end

def test_scopes_covers_is_truthy_for_same_scopes
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
read_orders_identical = ShopifyAPI::ApiAccess.new(%w(read_orders))

assert read_orders.covers?(read_orders_identical)
end

def test_covers_is_falsy_for_different_scopes
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
read_products = ShopifyAPI::ApiAccess.new(%w(read_products))

refute read_orders.covers?(read_products)
end

def test_covers_is_truthy_for_read_when_the_set_has_read_write
write_products = ShopifyAPI::ApiAccess.new(%w(write_products))
read_products = ShopifyAPI::ApiAccess.new(%w(read_products))

assert write_products.covers?(read_products)
end

def test_covers_is_truthy_for_read_when_the_set_has_read_write_for_that_resource_and_others
write_products_and_orders = ShopifyAPI::ApiAccess.new(%w(write_products, write_orders))
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))

assert write_products_and_orders.covers?(read_orders)
end

def test_covers_is_truthy_for_write_when_the_set_has_read_write_for_that_resource_and_others
write_products_and_orders = ShopifyAPI::ApiAccess.new(%w(write_products, write_orders))
write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))

assert write_products_and_orders.covers?(write_orders)
end

def test_covers_is_truthy_for_subset_of_scopes
write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
write_orders_products = ShopifyAPI::ApiAccess.new(%w(write_orders read_products))

assert write_products_orders_customers.covers?(write_orders_products)
end

def test_covers_is_falsy_for_sets_of_scopes_that_have_no_common_elements
write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
write_images_read_content = ShopifyAPI::ApiAccess.new(%w(write_images read_content))

refute write_products_orders_customers.covers?(write_images_read_content)
end

def test_covers_is_falsy_for_sets_of_scopes_that_have_only_some_common_access
write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
write_products_read_content = ShopifyAPI::ApiAccess.new(%w(write_products read_content))

refute write_products_orders_customers.covers?(write_products_read_content)
end

def test_duplicate_scopes_resolve_to_one_scope
read_orders_duplicated = ShopifyAPI::ApiAccess.new(%w(read_orders read_orders read_orders read_orders))
read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))

assert_equal read_orders, read_orders_duplicated
end

def test_to_s_outputs_scopes_as_a_comma_separated_list_without_implied_read_scopes
serialized_read_products_write_orders = "read_products,write_orders"
read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products read_orders write_orders))

assert_equal read_products_write_orders.to_s, serialized_read_products_write_orders
end

def test_to_a_outputs_scopes_as_an_array_of_strings_without_implied_read_scopes
serialized_read_products_write_orders = %w(write_orders read_products)
read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products read_orders write_orders))

assert_equal read_products_write_orders.to_a.sort, serialized_read_products_write_orders.sort
end

def test_creating_scopes_removes_extra_whitespace_from_scope_name_and_blank_scope_names
deserialized_read_products_write_orders = ShopifyAPI::ApiAccess.new([' read_products', ' ', 'write_orders '])
serialized_read_products_write_orders = deserialized_read_products_write_orders.to_s
expected_read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))

assert_equal expected_read_products_write_orders, ShopifyAPI::ApiAccess.new(serialized_read_products_write_orders)
end

def test_creating_scopes_from_a_string_works_with_a_comma_separated_list
deserialized_read_products_write_orders = ShopifyAPI::ApiAccess.new("read_products,write_orders")
serialized_read_products_write_orders = deserialized_read_products_write_orders.to_s
expected_read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))

assert_equal expected_read_products_write_orders, ShopifyAPI::ApiAccess.new(serialized_read_products_write_orders)
end

def test_using_to_s_from_one_scopes_to_construct_another_will_be_equal
read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))

assert_equal read_products_write_orders, ShopifyAPI::ApiAccess.new(read_products_write_orders.to_s)
end

def test_using_to_a_from_one_scopes_to_construct_another_will_be_equal
read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))

assert_equal read_products_write_orders, ShopifyAPI::ApiAccess.new(read_products_write_orders.to_a)
end
end