osso 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.buildkite/pipeline.yml +1 -0
- data/.rubocop.yml +4 -16
- data/Gemfile +3 -3
- data/Gemfile.lock +76 -70
- data/Rakefile +3 -0
- data/bin/console +3 -0
- data/db/schema.rb +2 -2
- data/lib/osso.rb +1 -0
- data/lib/osso/db/migrate/20201125143501_add_salesforce_to_provider_service_enum.rb +28 -0
- data/lib/osso/graphql/mutations/configure_identity_provider.rb +4 -1
- data/lib/osso/graphql/mutations/create_enterprise_account.rb +4 -1
- data/lib/osso/graphql/mutations/create_identity_provider.rb +8 -3
- data/lib/osso/graphql/mutations/create_oauth_client.rb +4 -1
- data/lib/osso/graphql/mutations/delete_enterprise_account.rb +4 -1
- data/lib/osso/graphql/mutations/delete_identity_provider.rb +4 -1
- data/lib/osso/graphql/mutations/delete_oauth_client.rb +4 -1
- data/lib/osso/graphql/mutations/invite_admin_user.rb +6 -0
- data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +10 -1
- data/lib/osso/graphql/mutations/set_redirect_uris.rb +2 -0
- data/lib/osso/graphql/mutations/update_app_config.rb +4 -1
- data/lib/osso/graphql/query.rb +26 -31
- data/lib/osso/graphql/schema.rb +0 -1
- data/lib/osso/graphql/types/identity_provider_service.rb +1 -0
- data/lib/osso/lib/analytics.rb +55 -0
- data/lib/osso/lib/route_map.rb +2 -0
- data/lib/osso/models/account.rb +1 -1
- data/lib/osso/models/identity_provider.rb +3 -2
- data/lib/osso/routes/admin.rb +37 -5
- data/lib/osso/routes/auth.rb +2 -0
- data/lib/osso/routes/oauth.rb +10 -4
- data/lib/osso/version.rb +1 -1
- data/lib/tasks/bootstrap.rake +6 -4
- data/osso-rb.gemspec +5 -3
- data/spec/graphql/mutations/create_identity_provider_spec.rb +1 -1
- data/spec/models/identity_provider_spec.rb +1 -0
- data/spec/routes/admin_spec.rb +27 -9
- data/spec/routes/auth_spec.rb +5 -3
- data/spec/routes/oauth_spec.rb +20 -12
- data/spec/spec_helper.rb +2 -0
- data/spec/support/views/hosted_login.erb +1 -0
- data/spec/support/views/saml_login_form.erb +1 -0
- metadata +40 -9
- data/spec/routes/app_spec.rb +0 -6
@@ -14,7 +14,10 @@ module Osso
|
|
14
14
|
def resolve(**args)
|
15
15
|
oauth_client = Osso::Models::OauthClient.new(args)
|
16
16
|
|
17
|
-
|
17
|
+
if oauth_client.save
|
18
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
|
19
|
+
return response_data(oauth_client: oauth_client)
|
20
|
+
end
|
18
21
|
|
19
22
|
response_error(oauth_client.errors)
|
20
23
|
end
|
@@ -18,7 +18,10 @@ module Osso
|
|
18
18
|
def resolve(**args)
|
19
19
|
customer = enterprise_account(**args)
|
20
20
|
|
21
|
-
|
21
|
+
if customer.destroy
|
22
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
|
23
|
+
return response_data(enterprise_account: nil)
|
24
|
+
end
|
22
25
|
|
23
26
|
response_error(customer.errors)
|
24
27
|
end
|
@@ -14,7 +14,10 @@ module Osso
|
|
14
14
|
def resolve(id:)
|
15
15
|
identity_provider = Osso::Models::IdentityProvider.find(id)
|
16
16
|
|
17
|
-
|
17
|
+
if identity_provider.destroy
|
18
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: { id: id })
|
19
|
+
return response_data(identity_provider: nil)
|
20
|
+
end
|
18
21
|
|
19
22
|
response_error(identity_provider.errors)
|
20
23
|
end
|
@@ -14,7 +14,10 @@ module Osso
|
|
14
14
|
def resolve(id:)
|
15
15
|
oauth_client = Osso::Models::OauthClient.find(id)
|
16
16
|
|
17
|
-
|
17
|
+
if oauth_client.destroy
|
18
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: { id: id })
|
19
|
+
return response_data(oauth_client: nil)
|
20
|
+
end
|
18
21
|
|
19
22
|
response_error(oauth_client.errors)
|
20
23
|
end
|
@@ -23,6 +23,12 @@ module Osso
|
|
23
23
|
if admin_user.save
|
24
24
|
verify_user(email)
|
25
25
|
|
26
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: {
|
27
|
+
invited_email: email,
|
28
|
+
invited_role: role,
|
29
|
+
invited_oauth_client_id: oauth_client_id,
|
30
|
+
})
|
31
|
+
|
26
32
|
return response_data(admin_user: admin_user)
|
27
33
|
end
|
28
34
|
|
@@ -15,7 +15,16 @@ module Osso
|
|
15
15
|
oauth_client = Osso::Models::OauthClient.find(id)
|
16
16
|
oauth_client.regenerate_secrets!
|
17
17
|
|
18
|
-
|
18
|
+
if oauth_client.save
|
19
|
+
Osso::Analytics.capture(
|
20
|
+
email: context[:email],
|
21
|
+
event: self.class.name.demodulize,
|
22
|
+
properties: {
|
23
|
+
oauth_client_id: id
|
24
|
+
}
|
25
|
+
)
|
26
|
+
return response_data(oauth_client: oauth_client)
|
27
|
+
end
|
19
28
|
|
20
29
|
response_error(oauth_client.errors)
|
21
30
|
end
|
@@ -18,6 +18,8 @@ module Osso
|
|
18
18
|
update_existing(oauth_client, redirect_uris)
|
19
19
|
create_new(oauth_client, redirect_uris)
|
20
20
|
|
21
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: redirect_uris)
|
22
|
+
|
21
23
|
response_data(oauth_client: oauth_client.reload)
|
22
24
|
rescue StandardError => e
|
23
25
|
response_error(e)
|
@@ -15,7 +15,10 @@ module Osso
|
|
15
15
|
|
16
16
|
def resolve(**args)
|
17
17
|
app_config = Osso::Models::AppConfig.find
|
18
|
-
|
18
|
+
if app_config.update(**args)
|
19
|
+
Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
|
20
|
+
return response_data(app_config: app_config)
|
21
|
+
end
|
19
22
|
|
20
23
|
response_error(app_config.errors)
|
21
24
|
end
|
data/lib/osso/graphql/query.rb
CHANGED
@@ -16,44 +16,39 @@ module Osso
|
|
16
16
|
|
17
17
|
field :oauth_clients, null: true, resolver: Resolvers::OAuthClients
|
18
18
|
|
19
|
-
field
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
field :admin_users, [Types::AdminUser], null: false
|
20
|
+
|
21
|
+
field :app_config, Types::AppConfig, null: false
|
22
|
+
|
23
|
+
field :current_user, Types::AdminUser, null: false
|
24
|
+
|
25
|
+
field :identity_provider, Types::IdentityProvider, null: true do
|
25
26
|
argument :id, ID, required: true
|
26
27
|
end
|
27
28
|
|
28
|
-
field
|
29
|
-
:app_config,
|
30
|
-
Types::AppConfig,
|
31
|
-
null: false,
|
32
|
-
resolve: ->(_obj, _args, _context) { Osso::Models::AppConfig.find },
|
33
|
-
)
|
34
|
-
|
35
|
-
field(
|
36
|
-
:oauth_client,
|
37
|
-
Types::OauthClient,
|
38
|
-
null: true,
|
39
|
-
resolve: ->(_obj, args, _context) { Osso::Models::OauthClient.find(args[:id]) },
|
40
|
-
) do
|
29
|
+
field :oauth_client, Types::OauthClient, null: true do
|
41
30
|
argument :id, ID, required: true
|
42
31
|
end
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
33
|
+
def admin_users
|
34
|
+
Osso::Models::Account.all
|
35
|
+
end
|
36
|
+
|
37
|
+
def app_config
|
38
|
+
Osso::Models::AppConfig.find
|
39
|
+
end
|
40
|
+
|
41
|
+
def current_user
|
42
|
+
context.to_h
|
43
|
+
end
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
def identity_provider(id:)
|
46
|
+
Osso::Models::IdentityProvider.find(id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def oauth_client(id:)
|
50
|
+
Osso::Models::OauthClient.find(id)
|
51
|
+
end
|
57
52
|
end
|
58
53
|
end
|
59
54
|
end
|
data/lib/osso/graphql/schema.rb
CHANGED
@@ -9,6 +9,7 @@ module Osso
|
|
9
9
|
value('OKTA', 'Okta Identity Provider', value: 'OKTA')
|
10
10
|
value('ONELOGIN', 'OneLogin Identity Provider', value: 'ONELOGIN')
|
11
11
|
value('PING', 'PingID Identity Provider', value: 'PING')
|
12
|
+
value('SALESFORCE', 'Salesforce Identity Provider', value: 'SALESFORCE')
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'posthog-ruby'
|
4
|
+
|
5
|
+
module Osso
|
6
|
+
# Osso::Analytics provides an interface to track product analytics for any provider.
|
7
|
+
# Osso recommends PostHog as an open source solution for your product analytics needs.
|
8
|
+
# If you want to use another product analytics provider, you can patch the Osso::Analytics
|
9
|
+
# class yourself in your parent application. Be sure to implement the public
|
10
|
+
# .identify and .capture class methods with the required method signatures and require
|
11
|
+
# your class after requiring Osso.
|
12
|
+
class Analytics
|
13
|
+
class << self
|
14
|
+
def identify(email:, properties: {})
|
15
|
+
return unless configured?
|
16
|
+
|
17
|
+
client.identify({
|
18
|
+
distinct_id: email,
|
19
|
+
properties: properties.merge(instance_properties),
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
def capture(email:, event:, properties: {})
|
24
|
+
return unless configured?
|
25
|
+
|
26
|
+
client.capture(
|
27
|
+
distinct_id: email,
|
28
|
+
event: event,
|
29
|
+
properties: properties.merge(instance_properties),
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def configured?
|
36
|
+
ENV['POSTHOG_API_KEY'].present?
|
37
|
+
end
|
38
|
+
|
39
|
+
def client
|
40
|
+
@client ||= PostHog::Client.new({
|
41
|
+
api_key: ENV['POSTHOG_API_KEY'],
|
42
|
+
api_host: ENV['POSTHOG_HOST'],
|
43
|
+
on_error: proc { |_status, msg| print msg },
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
def instance_properties
|
48
|
+
{
|
49
|
+
instance_url: ENV['BASE_URL'],
|
50
|
+
osso_plan: ENV['OSSO_PLAN'],
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/osso/lib/route_map.rb
CHANGED
data/lib/osso/models/account.rb
CHANGED
@@ -18,7 +18,7 @@ module Osso
|
|
18
18
|
|
19
19
|
ENTITY_ID_URI_REQUIRED = [
|
20
20
|
'PING',
|
21
|
-
]
|
21
|
+
].freeze
|
22
22
|
|
23
23
|
def name
|
24
24
|
service.titlecase
|
@@ -30,6 +30,7 @@ module Osso
|
|
30
30
|
idp_sso_target_url: sso_url,
|
31
31
|
idp_cert: sso_cert,
|
32
32
|
issuer: sso_issuer,
|
33
|
+
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
33
34
|
}
|
34
35
|
end
|
35
36
|
|
@@ -55,7 +56,7 @@ module Osso
|
|
55
56
|
|
56
57
|
def set_sso_issuer
|
57
58
|
parts = [domain, oauth_client_id]
|
58
|
-
|
59
|
+
|
59
60
|
parts.unshift('https:/') if ENTITY_ID_URI_REQUIRED.any?(service)
|
60
61
|
|
61
62
|
self.sso_issuer = parts.join('/')
|
data/lib/osso/routes/admin.rb
CHANGED
@@ -10,16 +10,45 @@ module Osso
|
|
10
10
|
DB = Sequel.postgres(extensions: :activerecord_connection)
|
11
11
|
use Rack::Session::Cookie, secret: ENV.fetch('SESSION_SECRET')
|
12
12
|
|
13
|
+
plugin :json
|
13
14
|
plugin :middleware
|
14
15
|
plugin :render, engine: 'erb', views: ENV['RODAUTH_VIEWS'] || DEFAULT_VIEWS_DIR
|
15
16
|
plugin :route_csrf
|
16
17
|
|
17
18
|
plugin :rodauth do
|
18
|
-
enable :login, :verify_account
|
19
|
+
enable :login, :verify_account, :jwt
|
20
|
+
|
21
|
+
base_uri = URI.parse(ENV.fetch('BASE_URL'))
|
22
|
+
base_url base_uri
|
23
|
+
domain base_uri.host
|
24
|
+
|
25
|
+
jwt_secret ENV.fetch('SESSION_SECRET')
|
26
|
+
only_json? false
|
27
|
+
|
28
|
+
email_from { "Osso <no-reply@#{domain}>" }
|
19
29
|
verify_account_set_password? true
|
20
|
-
already_logged_in { redirect login_redirect }
|
21
30
|
use_database_authentication_functions? false
|
22
31
|
|
32
|
+
after_login do
|
33
|
+
Osso::Analytics.identify(email: account[:email], properties: account)
|
34
|
+
end
|
35
|
+
|
36
|
+
verify_account_view do
|
37
|
+
render :admin
|
38
|
+
end
|
39
|
+
|
40
|
+
login_view do
|
41
|
+
render :admin
|
42
|
+
end
|
43
|
+
|
44
|
+
verify_account_email_subject do
|
45
|
+
DB[:accounts].one? ? 'Your Osso instance is ready' : 'You\'ve been invited to start using Osso'
|
46
|
+
end
|
47
|
+
|
48
|
+
verify_account_email_body do
|
49
|
+
DB[:accounts].one? ? render('verify-first-account-email') : render('verify-account-email')
|
50
|
+
end
|
51
|
+
|
23
52
|
before_create_account_route do
|
24
53
|
request.halt unless DB[:accounts].empty?
|
25
54
|
end
|
@@ -31,13 +60,16 @@ module Osso
|
|
31
60
|
r.rodauth
|
32
61
|
|
33
62
|
def current_account
|
34
|
-
Osso::Models::Account.find(
|
35
|
-
|
63
|
+
Osso::Models::Account.find(
|
64
|
+
rodauth.
|
65
|
+
session.
|
66
|
+
to_hash.
|
67
|
+
stringify_keys['account_id'],
|
68
|
+
).context.
|
36
69
|
merge({ rodauth: rodauth })
|
37
70
|
end
|
38
71
|
|
39
72
|
r.on 'admin' do
|
40
|
-
rodauth.require_authentication
|
41
73
|
erb :admin, layout: false
|
42
74
|
end
|
43
75
|
|
data/lib/osso/routes/auth.rb
CHANGED
@@ -14,6 +14,8 @@ 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
|
+
use Rack::Protection, allow_if: ->(env) { Rack::Request.new(env)&.path&.end_with?('callback') }
|
18
|
+
|
17
19
|
use OmniAuth::Builder do
|
18
20
|
OmniAuth::MultiProvider.register(
|
19
21
|
self,
|
data/lib/osso/routes/oauth.rb
CHANGED
@@ -16,13 +16,14 @@ module Osso
|
|
16
16
|
# Once they complete IdP login, they will be returned to the
|
17
17
|
# redirect_uri with an authorization code parameter.
|
18
18
|
get '/authorize' do
|
19
|
-
identity_providers = find_providers
|
20
|
-
|
21
19
|
validate_oauth_request(env)
|
22
20
|
|
23
|
-
|
21
|
+
return erb :hosted_login if render_hosted_login?
|
22
|
+
|
23
|
+
@providers = find_providers
|
24
|
+
|
25
|
+
return erb :saml_login_form if @providers.one?
|
24
26
|
|
25
|
-
@providers = identity_providers.not_pending
|
26
27
|
return erb :multiple_providers if @providers.count > 1
|
27
28
|
|
28
29
|
raise Osso::Error::MissingConfiguredIdentityProvider.new(domain: params[:domain])
|
@@ -61,6 +62,10 @@ module Osso
|
|
61
62
|
|
62
63
|
private
|
63
64
|
|
65
|
+
def render_hosted_login?
|
66
|
+
[params[:email], params[:domain]].all?(&:nil?)
|
67
|
+
end
|
68
|
+
|
64
69
|
def find_providers
|
65
70
|
if params[:email]
|
66
71
|
user = Osso::Models::User.
|
@@ -71,6 +76,7 @@ module Osso
|
|
71
76
|
|
72
77
|
Osso::Models::IdentityProvider.
|
73
78
|
joins(:oauth_client).
|
79
|
+
not_pending.
|
74
80
|
where(
|
75
81
|
domain: domain_from_params,
|
76
82
|
oauth_clients: { identifier: params[:client_id] },
|
data/lib/osso/version.rb
CHANGED
data/lib/tasks/bootstrap.rake
CHANGED
@@ -8,9 +8,11 @@ namespace :osso do
|
|
8
8
|
desc 'Bootstrap Osso data for a deployment'
|
9
9
|
task :bootstrap do
|
10
10
|
%w[Production Staging Development].each do |environment|
|
11
|
+
next if Osso::Models::OauthClient.find_by_name(environment)
|
12
|
+
|
11
13
|
Osso::Models::OauthClient.create!(
|
12
14
|
name: environment,
|
13
|
-
)
|
15
|
+
)
|
14
16
|
end
|
15
17
|
|
16
18
|
Osso::Models::AppConfig.create
|
@@ -18,7 +20,7 @@ namespace :osso do
|
|
18
20
|
admin_email = ENV['ADMIN_EMAIL']
|
19
21
|
|
20
22
|
if admin_email
|
21
|
-
|
23
|
+
Osso::Models::Account.create(
|
22
24
|
email: admin_email,
|
23
25
|
status_id: 1,
|
24
26
|
role: 'admin',
|
@@ -29,10 +31,10 @@ namespace :osso do
|
|
29
31
|
rodauth = Osso::Admin.rodauth.new(Osso::Admin.new({
|
30
32
|
'HTTP_HOST' => base_uri.host,
|
31
33
|
'SERVER_NAME' => base_uri.to_s,
|
32
|
-
'rack.url_scheme' => base_uri.scheme
|
34
|
+
'rack.url_scheme' => base_uri.scheme,
|
33
35
|
}))
|
34
36
|
|
35
|
-
|
37
|
+
rodauth.account_from_login(admin_email)
|
36
38
|
rodauth.setup_account_verification
|
37
39
|
end
|
38
40
|
end
|