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

Conversation

rezaansyed
Copy link
Contributor

@rezaansyed rezaansyed commented Jan 13, 2021

This is a follow up on our discussion: Shopify/omniauth-shopify-oauth2#96 (comment)

What does this PR solve?

There are common operations that are used for scopes on OmniAuth and apps consuming the gem. Such operations include serializing, deserializing and normalizing scopes. This PR introduces the Scopes value object that will be used to perform such operations. This helps ensure that apps do not potentially write duplicate code for scopes and delegating this responsibility to the Shopify OmniAuth gem.

Examples of improvements

Simplifying valid scope checks

Before

def valid_scope?(token)
  return false unless token && params[:scope] && token['scope']
  expected_scope = normalized_scopes(params[:scope]).sort
  (expected_scope == token['scope'].split(SCOPE_DELIMITER).sort)
end

def normalized_scopes(scopes)
  scope_list = scopes.to_s.split(SCOPE_DELIMITER).map(&:strip).reject(&:empty?).uniq
  ignore_scopes = scope_list.map { |scope| scope =~ /\A(unauthenticated_)?write_(.*)\z/ && "#{$1}read_#{$2}" }.compact
  scope_list - ignore_scopes
end

After

def valid_scope?(token)
  return false unless token && params[:scope] && token['scope']
  expected_api_access = ShopifyAPI::ApiAccess.new(config[:scope])
  actual_api_access = ShopifyAPI::ApiAccess.new(token['scope'])

  actual_api_access == expected_api_access
end

Delegating check for whether scopes are mismatched or subset of another

def scopes_mismatch?(existing_scopes, requesting_scopes)
  existing_access = ShopifyAPI::ApiAccess.new(existing_scopes)
  requested_access = ShopifyAPI::ApiAccess.new(requesting_scopes)
  
  existing_access == requested_access
end

def scopes_subset?(existing_scopes, requesting_scopes)
  existing_access = ShopifyAPI::ApiAccess.new(existing_scopes)
  requested_access = ShopifyAPI::ApiAccess.new(requesting_scopes)

  requested_access.covers?(existing_access)
end

Copy link
Contributor

@NabeelAhsen NabeelAhsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid!

Copy link
Contributor

@paulomarg paulomarg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Couple of nitpicks only.

# frozen_string_literal: true
require 'test_helper'

class ScopesTest < Minitest::Test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should this class be named ApiAccessTest?

end

def test_to_a_outputs_scopes_as_an_array_of_strings_without_implied_read_scopes
serialized_scopes = %w(read_products write_orders)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can either be a string or an array of scopes so that we can support two types of inputs when instantiating ApiAccess.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just wondering if it made sense for serialized_scopes to be an array - I thought the tests was slightly off in that but it seems like it's just the variable name that is ''''wrong''''. No reason to change it 🙂

@greatestape
Copy link

It's sort of hard to picture what the big picture is with this change.

Would it make sense to update the description to show a preview of how we could use this new feature in shopify_app and/or omniauth-shopify-oauth2?

@rezaansyed rezaansyed force-pushed the define-api-access branch 2 times, most recently from b0268b8 to e37e892 Compare January 25, 2021 20:56
Copy link

@greatestape greatestape left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@alexaitken alexaitken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small change to the test relying on the order of the elements from to_a.

serialized_read_products_write_orders = %w(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_a, serialized_read_products_write_orders
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert assumes the order of the elements. ApiAccess operates as a set, the order of the array can not be guaranteed.

There is no built in minitest assert for this, adding .sort to the end of each array will reorder the arrays before checking equality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants