Skip to content

Commit

Permalink
[DEV-2013] V4 updates (#16)
Browse files Browse the repository at this point in the history
* Update to MLH API v4

- Update endpoints to use v4 API
- Add support for expandable fields
- Update scopes to use new granular system
- Configure auth params to use request body
- Document offline_access scope for refresh tokens

* Update to MLH API v4

- Update endpoints to use v4 API
- Add support for expandable fields
- Update scopes to use new granular system
- Configure auth params to use request body
- Document offline_access scope for refresh tokens
- Add deep symbolization for nested arrays
- Include all fields from user object schema

* test: Add comprehensive test coverage for v4 API

- Added ActiveSupport for deep symbolization
- Updated test structure for v4 API compatibility
- Improved SimpleCov configuration
- Increased line coverage to 80.49%
- Added branch coverage tracking

Link to Devin run: https://preview.devin.ai/devin/3be39d31a19840d9ae7b7bebabe8c9c6

* test: Increase test coverage from 80.49% to 87.8%

- Add tests for complex nested arrays and hashes
- Add tests for hash pruning with nil values
- Add tests for completely empty hashes
- Add tests for version constant and module loading
- Add explicit tests for uid method
- Fix spec_helper.rb to properly load all files

* Replace subject with strategy in tests to fix RSpec/NamedSubject violations

* Apply RuboCop auto-corrections for code style improvements

* Fix RuboCop offenses and improve test infrastructure

- Refactored MLH strategy for better code organization
- Fixed hash indentation in shared examples
- Replaced unverified doubles with instance_double
- Moved spec files to correct directory structure
- All tests passing with 88.64% line coverage

* We don't need to support Ruby 2.7

* Update gem version to 2.0

* Correct json parsing

* fix: properly handle data wrapper in API response and update test infrastructure

* Revert "fix: properly handle data wrapper in API response and update test infrastructure"

This reverts commit 6cafa4e.

* fix: update test doubles to match strategy implementation

* style: fix argument alignment in spec file

* rubocop formatting

* add line breaks as per Rashika's request

* Change the style as per Rashika's request in #16 (comment)

* Update the README for v4

* add example for accessing user data

* README formatting fix

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
  • Loading branch information
2 people authored and erinosher committed Nov 15, 2024
1 parent 5983238 commit a8d2892
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 103 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: ['2.7', '3.2']
ruby: ['3.2']
steps:
- uses: actions/checkout@v2
- name: Set up Ruby ${{ matrix.ruby }}
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in omniauth-mlh.gemspec
gemspec
gem 'activesupport'
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
[![Test](https://github.com/MLH/omniauth-mlh/actions/workflows/test.yml/badge.svg)](https://github.com/MLH/omniauth-mlh/actions/workflows/test.yml)

This is the official [OmniAuth](https://github.com/omniauth/omniauth) strategy for
authenticating with [MyMLH](https://my.mlh.io). To use it, you'll need to
[register an application](https://my.mlh.io/oauth/applications) and obtain a OAuth Application ID and Secret from MyMLH.
authenticating with [MyMLH](https://my.mlh.io) in Ruby applications. To use it, you'll need to
[register an application](https://my.mlh.io/developers) and obtain a OAuth Application ID and Secret from MyMLH.

It now supports MyMLH API V3. [Read the MyMLH V3 docs here](https://my.mlh.io/docs).
It now supports MyMLH API V4. [Read the MyMLH V4 docs here](https://my.mlh.io/developers/docs).

Once you have done so, you can follow the instructions below:

## Requirements

This Gem requires your Ruby version to be at least `2.2.0`, which is set
downstream by [Omniauth](https://github.com/omniauth/omniauth/blob/master/omniauth.gemspec#L22).
This Gem requires your Ruby version to be at least `3.2.0`.

## Installation

Expand All @@ -34,9 +33,13 @@ Or install it yourself as:

## Usage (Rack)

You can find a list of potential scopes and expandable fields in the [docs](https://my.mlh.io/developers/docs). The below defaults are provided simply as an example.

```ruby
use OmniAuth::Builder do
provider :mlh, ENV['MY_MLH_KEY'], ENV['MY_MLH_SECRET'], scope: 'default email birthday'
provider :mlh, ENV['MY_MLH_KEY'], ENV['MY_MLH_SECRET'],
scope: 'public offline_access user:read:profile',
expand_fields: ['education']
end
```

Expand All @@ -46,10 +49,27 @@ end
# config/devise.rb

Devise.setup do |config|
config.provider :mlh, ENV['MY_MLH_KEY'], ENV['MY_MLH_SECRET'], scope: 'default email birthday'
config.provider :mlh, ENV['MY_MLH_KEY'], ENV['MY_MLH_SECRET'],
scope: 'public offline_access user:read:profile',
expand_fields: ['education']
end
```

## Accessing User Data
Once a user has been authorized and you have received a token in your callback, you may access the scoped information for that user via the info key on the request data, as per the below example from a simple Sinatra app:

```ruby
get '/auth/mlh/callback' do
auth = request.env['omniauth.auth']
user_data = auth['info']
first_name = user_data['first_name']
erb "
<h1>Hello #{first_name}</h1>"
end
```

You can find the full User object in the [docs](https://my.mlh.io/developers/docs).

## Contributing

For guidance on setting up a development environment and how to make a contribution to omniauth-mlh, see the [contributing guidelines](https://github.com/MLH/omniauth-mlh/blob/main/CONTRIBUTING.md).
Expand All @@ -61,6 +81,6 @@ We used part of [datariot/omniauth-paypal](http://github.com/datariot/omniauth-p
## Questions?

Have a question about the API or this library? Start by checking out the
[official MyMLH documentation](https://my.mlh.io/docs). If you still can't
[official MyMLH documentation](https://my.mlh.io/developers/docs). If you still can't
find an answer, tweet at [@MLHacks](http://twitter.com/mlhacks) or drop an
email to [[email protected]](mailto:[email protected]).
2 changes: 1 addition & 1 deletion lib/omniauth-mlh/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module OmniAuth
module MLH
VERSION = '1.0.1'
VERSION = '4.0.1'
end
end
101 changes: 79 additions & 22 deletions lib/omniauth/strategies/mlh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,99 @@

module OmniAuth
module Strategies
# MLH OAuth2 Strategy
#
# @example Basic Usage
# use OmniAuth::Builder do
# provider :mlh, ENV['MLH_KEY'], ENV['MLH_SECRET']
# end
#
# @example With Expandable Fields
# use OmniAuth::Builder do
# provider :mlh, ENV['MLH_KEY'], ENV['MLH_SECRET'],
# expand_fields: ['education', 'professional_experience']
# end
#
# @example With Refresh Tokens (offline access)
# use OmniAuth::Builder do
# provider :mlh, ENV['MLH_KEY'], ENV['MLH_SECRET'],
# scope: 'user:read:profile offline_access'
# end
#
# When offline_access scope is requested, the strategy will include
# refresh_token in the credentials hash if provided by the server.
class MLH < OmniAuth::Strategies::OAuth2 # :nodoc:
option :name, :mlh

option :client_options, {
site: 'https://my.mlh.io',
authorize_url: 'oauth/authorize',
token_url: 'oauth/token'
token_url: 'oauth/token',
auth_scheme: :request_body # Change from basic auth to request body
}

# Support expandable fields through options
option :expand_fields, []

uid { data[:id] }

info do
data.slice(
:email,
:created_at,
:updated_at,
:first_name,
:last_name,
:level_of_study,
:major,
:date_of_birth,
:gender,
:phone_number,
:profession_type,
:company_name,
:company_title,
:scopes,
:school
)
{
# Basic fields
id: data[:id],
created_at: data[:created_at],
updated_at: data[:updated_at],
first_name: data[:first_name],
last_name: data[:last_name],
email: data[:email],
phone_number: data[:phone_number],
roles: data[:roles],

# Expandable fields
profile: data[:profile],
address: data[:address],
social_profiles: data[:social_profiles],
professional_experience: data[:professional_experience],
education: data[:education],
identifiers: data[:identifiers]
}
end

def data
@data ||= begin
access_token.get('/api/v3/user.json').parsed.deep_symbolize_keys[:data]
rescue StandardError
{}
@data ||= fetch_and_process_data.compact
rescue StandardError
{}
end

private

def fetch_and_process_data
response = access_token.get(build_api_url)
data = JSON.parse(response.body, symbolize_names: true)
return {} unless data.is_a?(Hash)

symbolize_nested_arrays(data)
end

def build_api_url
url = 'https://api.mlh.com/v4/users/me'
expand_fields = options[:expand_fields] || []
return url if expand_fields.empty?

expand_query = expand_fields.map { |f| "expand[]=#{f}" }.join('&')
"#{url}?#{expand_query}"
end

def symbolize_nested_arrays(hash)
hash.transform_values do |value|
case value
when Hash
symbolize_nested_arrays(value)
when Array
value.map { |item| item.is_a?(Hash) ? symbolize_nested_arrays(item) : item }
else
value
end
end
end
end
Expand Down
38 changes: 7 additions & 31 deletions spec/omni_auth/mlh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,14 @@

require 'spec_helper'

describe OmniAuth::MLH do
subject(:omniauth_mlh) do
# The instance variable @options is being used to pass options to the subject of the shared examples
OmniAuth::Strategies::MLH.new(nil, @options || {}) # rubocop:disable RSpec/InstanceVariable
RSpec.describe OmniAuth::MLH do
it 'has a version number' do
expect(OmniAuth::MLH::VERSION).not_to be_nil
expect(OmniAuth::MLH::VERSION).to eq('4.0.1')
end

it_behaves_like 'an oauth2 strategy'

describe '#client' do
it 'has correct MyMLH site' do
expect(omniauth_mlh.client.site).to eq('https://my.mlh.io')
end

it 'has correct authorize url' do
expect(omniauth_mlh.client.options[:authorize_url]).to eq('oauth/authorize')
end

it 'has correct token url' do
expect(omniauth_mlh.client.options[:token_url]).to eq('oauth/token')
end

it 'runs the setup block if passed one' do
counter = ''
@options = { setup: proc { |_env| counter = 'ok' } }
omniauth_mlh.setup_phase
expect(counter).to eq('ok')
end
end

describe '#callback_path' do
it 'has the correct callback path' do
expect(omniauth_mlh.callback_path).to eq('/auth/mlh/callback')
end
it 'loads the MLH strategy' do
expect(OmniAuth::Strategies::MLH).to be_a(Class)
expect(OmniAuth::Strategies::MLH.superclass).to eq(OmniAuth::Strategies::OAuth2)
end
end
Loading

0 comments on commit a8d2892

Please sign in to comment.