osso 0.0.3.12 → 0.0.3.17
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 +4 -4
- data/.buildkite/pipeline.yml +8 -1
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +2 -2
- data/bin/publish +18 -0
- data/lib/osso/graphql/mutation.rb +7 -3
- data/lib/osso/graphql/mutations.rb +1 -3
- data/lib/osso/graphql/mutations/base_mutation.rb +18 -5
- data/lib/osso/graphql/mutations/configure_identity_provider.rb +8 -10
- data/lib/osso/graphql/mutations/create_enterprise_account.rb +2 -0
- data/lib/osso/graphql/mutations/create_identity_provider.rb +14 -5
- data/lib/osso/graphql/mutations/create_oauth_client.rb +1 -3
- data/lib/osso/graphql/mutations/delete_enterprise_account.rb +9 -11
- data/lib/osso/graphql/mutations/delete_oauth_client.rb +1 -3
- data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +3 -5
- data/lib/osso/graphql/mutations/set_redirect_uris.rb +52 -0
- data/lib/osso/graphql/query.rb +7 -0
- data/lib/osso/graphql/resolvers.rb +1 -0
- data/lib/osso/graphql/resolvers/base_resolver.rb +21 -0
- data/lib/osso/graphql/resolvers/enterprise_account.rb +1 -11
- data/lib/osso/graphql/resolvers/enterprise_accounts.rb +2 -2
- data/lib/osso/graphql/resolvers/oauth_clients.rb +2 -2
- data/lib/osso/graphql/types.rb +2 -1
- data/lib/osso/graphql/types/admin_user.rb +22 -0
- data/lib/osso/graphql/types/base_object.rb +22 -0
- data/lib/osso/graphql/types/enterprise_account.rb +0 -5
- data/lib/osso/graphql/types/identity_provider.rb +0 -6
- data/lib/osso/graphql/types/oauth_client.rb +2 -4
- data/lib/osso/graphql/types/redirect_uri.rb +2 -4
- data/lib/osso/graphql/types/redirect_uri_input.rb +16 -0
- data/lib/osso/helpers/auth.rb +34 -15
- data/lib/osso/lib/route_map.rb +2 -2
- data/lib/osso/models/identity_provider.rb +6 -12
- data/lib/osso/models/oauth_client.rb +5 -0
- data/lib/osso/models/redirect_uri.rb +0 -11
- data/lib/osso/routes/admin.rb +2 -2
- data/lib/osso/routes/auth.rb +29 -12
- data/lib/osso/routes/oauth.rb +25 -18
- data/lib/osso/version.rb +1 -1
- data/spec/graphql/mutations/configure_identity_provider_spec.rb +17 -4
- data/spec/graphql/mutations/create_enterprise_account_spec.rb +13 -4
- data/spec/graphql/mutations/create_identity_provider_spec.rb +18 -6
- data/spec/graphql/mutations/create_oauth_client_spec.rb +10 -3
- data/spec/graphql/mutations/delete_enterprise_account_spec.rb +18 -4
- data/spec/graphql/mutations/delete_oauth_client_spec.rb +8 -4
- data/spec/graphql/query/enterprise_account_spec.rb +21 -6
- data/spec/graphql/query/enterprise_accounts_spec.rb +4 -2
- data/spec/graphql/query/identity_provider_spec.rb +16 -6
- data/spec/graphql/query/oauth_clients_spec.rb +10 -7
- data/spec/models/identity_provider_spec.rb +12 -0
- data/spec/routes/auth_spec.rb +18 -0
- data/spec/routes/oauth_spec.rb +5 -2
- data/spec/support/views/error.erb +0 -0
- metadata +12 -9
- data/lib/osso/graphql/mutations/add_redirect_uris_to_oauth_client.rb +0 -39
- data/lib/osso/graphql/mutations/delete_redirect_uri.rb +0 -38
- data/lib/osso/graphql/mutations/mark_redirect_uri_primary.rb +0 -34
- data/lib/osso/graphql/types/user.rb +0 -17
data/lib/osso/graphql/types.rb
CHANGED
@@ -9,10 +9,11 @@ require_relative 'types/base_connection'
|
|
9
9
|
require_relative 'types/base_object'
|
10
10
|
require_relative 'types/base_enum'
|
11
11
|
require_relative 'types/base_input_object'
|
12
|
+
require_relative 'types/admin_user'
|
12
13
|
require_relative 'types/identity_provider_service'
|
13
14
|
require_relative 'types/identity_provider_status'
|
14
15
|
require_relative 'types/identity_provider'
|
15
16
|
require_relative 'types/enterprise_account'
|
16
17
|
require_relative 'types/redirect_uri'
|
18
|
+
require_relative 'types/redirect_uri_input'
|
17
19
|
require_relative 'types/oauth_client'
|
18
|
-
require_relative 'types/user'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
|
5
|
+
module Osso
|
6
|
+
module GraphQL
|
7
|
+
module Types
|
8
|
+
class AdminUser < Types::BaseObject
|
9
|
+
description 'An Admin User of Osso'
|
10
|
+
|
11
|
+
field :id, ID, null: false
|
12
|
+
field :email, String, null: false
|
13
|
+
field :scope, String, null: false
|
14
|
+
field :oauth_client_id, ID, null: true
|
15
|
+
|
16
|
+
def self.authorized?(_object, _context)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -10,6 +10,28 @@ module Osso
|
|
10
10
|
|
11
11
|
field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false
|
12
12
|
field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false
|
13
|
+
|
14
|
+
def self.admin_authorized?(context)
|
15
|
+
context[:scope] == 'admin'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.internal_authorized?(context)
|
19
|
+
%w[admin internal].include?(context[:scope])
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.enterprise_authorized?(context, domain)
|
23
|
+
return false unless domain
|
24
|
+
|
25
|
+
context[:email].split('@')[1] == domain
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.authorized?(object, context)
|
29
|
+
# we first receive the payload object as a hash, but can depend on the
|
30
|
+
# return type to hide the actual objects non-admins shouldn't see
|
31
|
+
return true if object.class == Hash
|
32
|
+
|
33
|
+
internal_authorized?(context) || enterprise_authorized?(context, object&.domain)
|
34
|
+
end
|
13
35
|
end
|
14
36
|
end
|
15
37
|
end
|
@@ -9,7 +9,6 @@ module Osso
|
|
9
9
|
description 'An Account for a company that wishes to use SAML via Osso'
|
10
10
|
implements ::GraphQL::Types::Relay::Node
|
11
11
|
|
12
|
-
global_id_field :gid
|
13
12
|
field :id, ID, null: false
|
14
13
|
field :name, String, null: false
|
15
14
|
field :domain, String, null: false
|
@@ -23,10 +22,6 @@ module Osso
|
|
23
22
|
def identity_providers
|
24
23
|
object.identity_providers
|
25
24
|
end
|
26
|
-
|
27
|
-
def self.authorized?(object, context)
|
28
|
-
super && (context[:scope] == :admin || object.domain == context[:scope])
|
29
|
-
end
|
30
25
|
end
|
31
26
|
end
|
32
27
|
end
|
@@ -7,9 +7,7 @@ module Osso
|
|
7
7
|
module Types
|
8
8
|
class IdentityProvider < Types::BaseObject
|
9
9
|
description 'Represents a SAML based IDP instance for an EnterpriseAccount'
|
10
|
-
implements ::GraphQL::Types::Relay::Node
|
11
10
|
|
12
|
-
global_id_field :gid
|
13
11
|
field :id, ID, null: false
|
14
12
|
field :enterprise_account_id, ID, null: false
|
15
13
|
field :service, Types::IdentityProviderService, null: true
|
@@ -23,10 +21,6 @@ module Osso
|
|
23
21
|
def documentation_pdf_url
|
24
22
|
ENV['BASE_URL'] + '/identity_provider/documentation/' + @object.id
|
25
23
|
end
|
26
|
-
|
27
|
-
def self.authorized?(object, context)
|
28
|
-
super && (context[:scope] == :admin || object.domain == context[:scope])
|
29
|
-
end
|
30
24
|
end
|
31
25
|
end
|
32
26
|
end
|
@@ -7,9 +7,7 @@ module Osso
|
|
7
7
|
module Types
|
8
8
|
class OauthClient < Types::BaseObject
|
9
9
|
description 'An OAuth client used to consume Osso SAML users'
|
10
|
-
implements ::GraphQL::Types::Relay::Node
|
11
10
|
|
12
|
-
global_id_field :gid
|
13
11
|
field :id, ID, null: false
|
14
12
|
field :name, String, null: false
|
15
13
|
field :client_id, String, null: false
|
@@ -24,8 +22,8 @@ module Osso
|
|
24
22
|
object.secret
|
25
23
|
end
|
26
24
|
|
27
|
-
def self.authorized?(
|
28
|
-
|
25
|
+
def self.authorized?(_object, context)
|
26
|
+
admin_authorized?(context)
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -7,15 +7,13 @@ module Osso
|
|
7
7
|
module Types
|
8
8
|
class RedirectUri < Types::BaseObject
|
9
9
|
description 'An allowed redirect URI for an OauthClient'
|
10
|
-
implements ::GraphQL::Types::Relay::Node
|
11
10
|
|
12
|
-
global_id_field :gid
|
13
11
|
field :id, ID, null: false
|
14
12
|
field :uri, String, null: false
|
15
13
|
field :primary, Boolean, null: false
|
16
14
|
|
17
|
-
def self.authorized?(
|
18
|
-
|
15
|
+
def self.authorized?(_object, context)
|
16
|
+
context[:scope] == 'admin'
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
|
5
|
+
module Osso
|
6
|
+
module GraphQL
|
7
|
+
module Types
|
8
|
+
class RedirectUrisInput < Types::BaseInputObject
|
9
|
+
description 'Attributes for creating or updating a collection of redirect URIs for an Oauth Client'
|
10
|
+
argument :id, ID, 'Database ID', required: false
|
11
|
+
argument :uri, String, 'URI value', required: true
|
12
|
+
argument :primary, Boolean, 'Whether the URI is the primary uri used in IDP initiated login', required: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/osso/helpers/auth.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pry'
|
3
4
|
module Osso
|
4
5
|
module Helpers
|
5
6
|
module Auth
|
6
|
-
|
7
|
+
END_USER_SCOPE = 'end-user'
|
8
|
+
INTERNAL_SCOPE = 'internal'
|
9
|
+
ADMIN_SCOPE = 'admin'
|
10
|
+
|
11
|
+
attr_accessor :current_user
|
12
|
+
|
13
|
+
def token_protected!
|
14
|
+
decode(token)
|
15
|
+
end
|
7
16
|
|
8
17
|
def enterprise_protected!(domain = nil)
|
9
18
|
return if admin_authorized?
|
19
|
+
return if internal_authorized?
|
10
20
|
return if enterprise_authorized?(domain)
|
11
21
|
|
12
22
|
halt 401 if request.post?
|
@@ -14,14 +24,26 @@ module Osso
|
|
14
24
|
redirect ENV['JWT_URL']
|
15
25
|
end
|
16
26
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
def enterprise_authorized?(domain)
|
28
|
+
decode(token)
|
29
|
+
|
30
|
+
@current_user[:scope] == END_USER_SCOPE &&
|
31
|
+
@current_user[:email].split('@')[1] == domain
|
32
|
+
rescue JWT::DecodeError
|
33
|
+
false
|
34
|
+
end
|
21
35
|
|
22
|
-
|
36
|
+
def internal_protected!
|
37
|
+
return if admin_authorized?
|
38
|
+
return if internal_authorized?
|
23
39
|
|
24
|
-
|
40
|
+
redirect ENV['JWT_URL']
|
41
|
+
end
|
42
|
+
|
43
|
+
def internal_authorized?
|
44
|
+
decode(token)
|
45
|
+
|
46
|
+
@current_user[:scope] == INTERNAL_SCOPE
|
25
47
|
rescue JWT::DecodeError
|
26
48
|
false
|
27
49
|
end
|
@@ -33,14 +55,9 @@ module Osso
|
|
33
55
|
end
|
34
56
|
|
35
57
|
def admin_authorized?
|
36
|
-
|
58
|
+
decode(token)
|
37
59
|
|
38
|
-
|
39
|
-
@current_scope = :admin
|
40
|
-
return true
|
41
|
-
end
|
42
|
-
|
43
|
-
false
|
60
|
+
@current_user[:scope] == ADMIN_SCOPE
|
44
61
|
rescue JWT::DecodeError
|
45
62
|
false
|
46
63
|
end
|
@@ -60,12 +77,14 @@ module Osso
|
|
60
77
|
end
|
61
78
|
|
62
79
|
def decode(token)
|
63
|
-
JWT.decode(
|
80
|
+
payload, _args = JWT.decode(
|
64
81
|
token,
|
65
82
|
ENV['JWT_HMAC_SECRET'],
|
66
83
|
true,
|
67
84
|
{ algorithm: 'HS256' },
|
68
85
|
)
|
86
|
+
|
87
|
+
@current_user = payload.symbolize_keys
|
69
88
|
end
|
70
89
|
end
|
71
90
|
end
|
data/lib/osso/lib/route_map.rb
CHANGED
@@ -11,12 +11,12 @@ module Osso
|
|
11
11
|
use Osso::Oauth
|
12
12
|
|
13
13
|
post '/graphql' do
|
14
|
-
|
14
|
+
token_protected!
|
15
15
|
|
16
16
|
result = Osso::GraphQL::Schema.execute(
|
17
17
|
params[:query],
|
18
18
|
variables: params[:variables],
|
19
|
-
context:
|
19
|
+
context: current_user.symbolize_keys,
|
20
20
|
)
|
21
21
|
|
22
22
|
json result
|
@@ -19,20 +19,14 @@ module Osso
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def saml_options
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
{
|
23
|
+
domain: domain,
|
24
|
+
idp_sso_target_url: sso_url,
|
25
|
+
idp_cert: sso_cert,
|
26
|
+
issuer: domain,
|
27
|
+
}
|
27
28
|
end
|
28
29
|
|
29
|
-
# def saml_options
|
30
|
-
# raise(
|
31
|
-
# NoMethodError,
|
32
|
-
# '#saml_options must be defined on each provider specific subclass',
|
33
|
-
# )
|
34
|
-
# end
|
35
|
-
|
36
30
|
def assertion_consumer_service_url
|
37
31
|
[
|
38
32
|
ENV.fetch('BASE_URL'),
|
@@ -5,6 +5,7 @@ module Osso
|
|
5
5
|
module Models
|
6
6
|
class OauthClient < ActiveRecord::Base
|
7
7
|
has_many :access_tokens
|
8
|
+
has_many :enterprise_accounts
|
8
9
|
has_many :refresh_tokens
|
9
10
|
has_many :identity_providers
|
10
11
|
has_many :redirect_uris
|
@@ -17,6 +18,10 @@ module Osso
|
|
17
18
|
redirect_uris.find(&:primary)
|
18
19
|
end
|
19
20
|
|
21
|
+
def redirect_uri_values
|
22
|
+
redirect_uris.map(&:uri)
|
23
|
+
end
|
24
|
+
|
20
25
|
def generate_secrets
|
21
26
|
self.identifier = SecureRandom.hex(16)
|
22
27
|
self.secret = SecureRandom.hex(32)
|
@@ -4,17 +4,6 @@ module Osso
|
|
4
4
|
module Models
|
5
5
|
class RedirectUri < ActiveRecord::Base
|
6
6
|
belongs_to :oauth_client
|
7
|
-
|
8
|
-
# TODO
|
9
|
-
# before_validation :set_primary, on: :creaet, :update
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def set_primary
|
14
|
-
if primary_was.true? && primary.false?
|
15
|
-
|
16
|
-
end
|
17
|
-
end
|
18
7
|
end
|
19
8
|
end
|
20
9
|
end
|
data/lib/osso/routes/admin.rb
CHANGED
data/lib/osso/routes/auth.rb
CHANGED
@@ -14,10 +14,6 @@ module Osso
|
|
14
14
|
/[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
|
15
15
|
freeze
|
16
16
|
|
17
|
-
def self.internal_redirect?(env)
|
18
|
-
env['HTTP_REFERER']&.match(env['SERVER_NAME'])
|
19
|
-
end
|
20
|
-
|
21
17
|
use OmniAuth::Builder do
|
22
18
|
OmniAuth::MultiProvider.register(
|
23
19
|
self,
|
@@ -26,21 +22,24 @@ module Osso
|
|
26
22
|
path_prefix: '/auth/saml',
|
27
23
|
callback_suffix: 'callback',
|
28
24
|
) do |identity_provider_id, _env|
|
29
|
-
|
30
|
-
|
25
|
+
Models::IdentityProvider.find(identity_provider_id).
|
26
|
+
saml_options
|
31
27
|
end
|
32
28
|
end
|
33
29
|
|
34
|
-
namespace '/auth' do
|
30
|
+
namespace '/auth' do # rubocop:disable Metrics/BlockLength
|
31
|
+
get '/failure' do
|
32
|
+
@error = params[:message]
|
33
|
+
erb :error
|
34
|
+
end
|
35
35
|
# Enterprise users are sent here after authenticating against
|
36
36
|
# their Identity Provider. We find or create a user record,
|
37
37
|
# and then create an authorization code for that user. The user
|
38
38
|
# is redirected back to your application with this code
|
39
|
-
# as a URL query param, which you then
|
39
|
+
# as a URL query param, which you then exchange for an access token.
|
40
40
|
post '/saml/:id/callback' do
|
41
41
|
provider = Models::IdentityProvider.find(params[:id])
|
42
|
-
oauth_client = provider.oauth_client
|
43
|
-
redirect_uri = env['redirect_uri'] || oauth_client.default_redirect_uri.uri
|
42
|
+
@oauth_client = provider.oauth_client
|
44
43
|
|
45
44
|
attributes = env['omniauth.auth']&.
|
46
45
|
extra&.
|
@@ -56,11 +55,29 @@ module Osso
|
|
56
55
|
end
|
57
56
|
|
58
57
|
authorization_code = user.authorization_codes.create!(
|
59
|
-
oauth_client: oauth_client,
|
58
|
+
oauth_client: @oauth_client,
|
60
59
|
redirect_uri: redirect_uri,
|
61
60
|
)
|
62
61
|
|
63
|
-
|
62
|
+
# Mark IDP as active
|
63
|
+
|
64
|
+
redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{provider_state}")
|
65
|
+
end
|
66
|
+
|
67
|
+
def redirect_uri
|
68
|
+
return @oauth_client.primary_redirect_uri.uri if valid_idp_initiated_flow
|
69
|
+
|
70
|
+
session[:osso_oauth_redirect_uri]
|
71
|
+
end
|
72
|
+
|
73
|
+
def provider_state
|
74
|
+
return @provider_state = 'IDP_INITIATED' if valid_idp_initiated_flow
|
75
|
+
|
76
|
+
session.delete(:osso_oauth_state)
|
77
|
+
end
|
78
|
+
|
79
|
+
def valid_idp_initiated_flow
|
80
|
+
!session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
|
64
81
|
end
|
65
82
|
end
|
66
83
|
end
|
data/lib/osso/routes/oauth.rb
CHANGED
@@ -6,38 +6,45 @@ module Osso
|
|
6
6
|
class Oauth < Sinatra::Base
|
7
7
|
include AppConfig
|
8
8
|
register Sinatra::Namespace
|
9
|
-
|
10
|
-
namespace '/oauth' do
|
9
|
+
|
10
|
+
namespace '/oauth' do # rubocop:disable Metrics/BlockLength
|
11
11
|
# Send your users here in order to being an authentication
|
12
12
|
# flow. This flow follows the authorization grant oauth
|
13
13
|
# spec with one exception - you must also pass the domain
|
14
|
-
# of the user who wants to sign in.
|
14
|
+
# of the user who wants to sign in. If the sign in request
|
15
|
+
# is valid, the user is redirected to their Identity Provider.
|
16
|
+
# Once they complete IdP login, they will be returned to the
|
17
|
+
# redirect_uri with an authorization code parameter.
|
15
18
|
get '/authorize' do
|
16
|
-
@enterprise = Models::EnterpriseAccount.
|
17
|
-
includes(:identity_providers).
|
18
|
-
find_by!(domain: params[:domain])
|
19
|
-
|
20
19
|
Rack::OAuth2::Server::Authorize.new do |req, _res|
|
21
20
|
client = Models::OauthClient.find_by!(identifier: req.client_id)
|
22
|
-
req.verify_redirect_uri!(client.redirect_uri_values)
|
21
|
+
session[:osso_oauth_redirect_uri] = req.verify_redirect_uri!(client.redirect_uri_values)
|
22
|
+
session[:osso_oauth_state] = params[:state]
|
23
23
|
end.call(env)
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
enterprise = Models::EnterpriseAccount.
|
26
|
+
includes(:identity_providers).
|
27
|
+
find_by!(domain: params[:domain])
|
28
|
+
|
29
|
+
redirect "/auth/saml/#{enterprise.provider.id}" if enterprise.single_provider?
|
29
30
|
|
30
31
|
# TODO: multiple provider support
|
31
32
|
# erb :multiple_providers
|
32
33
|
|
33
34
|
rescue Rack::OAuth2::Server::Authorize::BadRequest => e
|
34
35
|
@error = e
|
35
|
-
|
36
|
+
erb :error
|
37
|
+
rescue ActiveRecord::RecordNotFound => e
|
38
|
+
@error = e
|
39
|
+
@error = 'No OAuth Client exists for the provided client_id' if e.model == 'Osso::Models::OauthClient'
|
40
|
+
@error = "No Customer exists with the domain #{params[:domain]}" if e.model == 'Osso::Models::EnterpriseAccount'
|
41
|
+
erb :error
|
36
42
|
end
|
37
43
|
|
38
|
-
# Exchange an authorization code
|
39
|
-
# In addition to the
|
40
|
-
# required by
|
44
|
+
# Exchange an authorization code for an access token.
|
45
|
+
# In addition to the authorization code, you must include all
|
46
|
+
# paramaters required by OAuth spec: redirect_uri, client ID,
|
47
|
+
# and client secret
|
41
48
|
post '/token' do
|
42
49
|
Rack::OAuth2::Server::Token.new do |req, res|
|
43
50
|
code = Models::AuthorizationCode.
|
@@ -49,7 +56,8 @@ module Osso
|
|
49
56
|
end.call(env)
|
50
57
|
end
|
51
58
|
|
52
|
-
# Use the access token to request a user
|
59
|
+
# Use the access token to request a profile for the user who
|
60
|
+
# just logged in. Access tokens are short-lived.
|
53
61
|
get '/me' do
|
54
62
|
json Models::AccessToken.
|
55
63
|
includes(:user).
|
@@ -60,4 +68,3 @@ module Osso
|
|
60
68
|
end
|
61
69
|
end
|
62
70
|
end
|
63
|
-
# rubocop:enable Metrics/BlockLength
|