Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Add support for OAuth/OpenID Connect #1300

Merged
merged 18 commits into from
Sep 8, 2017
Merged

Add support for OAuth/OpenID Connect #1300

merged 18 commits into from
Sep 8, 2017

Conversation

Vad1mo
Copy link
Contributor

@Vad1mo Vad1mo commented Jun 10, 2017

This PR adds OAuth Social Logins via omniauth.

OAuth Providers:

  • OpenID Connect
  • Google
  • GitHub
  • Bitbucket
  • GitLab

Closes #645
Foundation for #527

Features:

  • During his first signup the user can change/set his username
  • Allow to restrict signups to a specific domain. From user with a @organisation.com email address.
  • GitHub/GitLab, Option to restrict signups to users that are part of an organisation and team.
  • Google, Option to restrict signups to users in a domain.

Social Login Buttons:
bildschirmfoto 2017-07-26 um 08 55 10

Option for the user the choose/modify the Username before the account is finally created.

bildschirmfoto 2017-07-26 um 08 57 38

New configuration Options

# OAuth support.
oauth:
  # If enabled, users can authenticate with their Google Account.
  google_oauth2:
    enabled: false
    # Credentials. Details on https://developers.google.com/identity/protocols/OpenIDConnect
    id: ""
    secret: ""
    # If a domain (e.g. mycompany.com) is set, then only signups with email from this domain are allowed.
    domain: ""
    options:
      # G Suite domain. If set, then only members of the domain can sign in/up.
      # If it's empty then any google users con sign in/up.
      hd: ""
  
  # OpenID authentication support. If enabled, then users can authenticate with OpenID/Connect
  open_id:
    enabled: false
    # Optional. If identifier set then user redirect to the OpenID provider.
    # If not, then user is asked for identifier before redirect.
    # Example https://openid.stackexchange.com
    identifier: ""
    # If a domain (e.g. mycompany.com) is set, then only signups with email from this domain are allowed.
    domain: ""

  # Github authentication support.
  github:
    enabled: false
    # Application credentials.
    key: ""
    secret: ""
    # Only members of organization's team can sign in/up with Github.
    organization: ""
    team: ""
     # If a domain (e.g. mycompany.com) is set, then only signups with email from this domain are allowed.
    domain: ""

  # Gitlab authentication support.
  gitlab:
    enabled: false
    id: ""
    secret: ""
    # Only member of the group can sign in/up with Gitlab.
    group: ""
     # If a domain (e.g. mycompany.com) is set, then only signups with email from this domain are allowed.
    domain: ""

@Vad1mo Vad1mo changed the title WIP: OAuth and OpenID Connect support with omniauth OAuth and OpenID Connect support with omniauth Jul 30, 2017
@Vad1mo Vad1mo changed the title OAuth and OpenID Connect support with omniauth Add support for OAuth/OpenID Connect Jul 30, 2017
README.md Outdated
@@ -49,7 +49,7 @@ setups in which you can deploy Portus.
Some highlights:

- [Synchronization with your private registry in order to fetch which images and tags are available](http://port.us.org/features/1_Synchronizing-the-Registry-and-Portus.html).
- [LDAP user authentication](http://port.us.org/features/2_LDAP-support.html).
- [LDAP, OAuth, OpenID-Connect user authentication](http://port.us.org/features/2_LDAP-support.html).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that the link is for LDAP only. We can write another page for other services later. In the meantime, move the OAuth, OpenID-Connect into another point (without link then).


def gitlab
auth_user
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of:

def google_oauth2
  auth_user
end

def open_id
  auth_user
end

def github
  auth_user
end

def gitlab
  auth_user
end

Do the following:

alias_method :google_oauth2, :auth_user
alias_method :open_id, :auth_user
alias_method :github, :auth_user
alias_method :gitlab, :auth_user


private

def auth_user
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add documentation

def new
return new_user_session_url unless (data = oauth_data)
@user = User.new username: data["info"]["username"], display_name: data["info"]["name"]
@user.suggest_username data["info"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer something like this:

def new
  data = oauth_data

  if data
    redirect_to new_user_session_url
  else
    @user = User.new username: data["info"]["username"], display_name: data["info"]["name"]
    @user.suggest_username data["info"]
  end
end

Mainly because I think doing this (data = oauth_data) is super ugly 😀. Plus, since it's a short method, we don't have to write it with an early return when a good ol' if/else statement would do it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, since suggest_username might return a generated username, make sure to show a flashy message being more explicit with it. Like "Successfully registered as myusername!"

@@ -213,6 +217,27 @@ def update_activities!(owner)
parameters: { username: username }
end

def self.create_from_oauth(params, data)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add documentation on this ? Things like where does this data come from, the expected values, etc.

User.create params
end

# If usernsme exists then try variant username + "_nn".
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo: usernsme -> username.

if objs.length == 2
return false if !self[objs[0]][objs[1]] || self[objs[0]][objs[1]].empty?
self[objs[0]][objs[1]]["enabled"].eql?(true)
else
return false if !self[feature] || self[feature].empty?
# return false if !self[feature] || self[feature].empty?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't you just remove this ?

@mssola
Copy link
Collaborator

mssola commented Aug 1, 2017

Overall it looks good to me. Just fix my issues. Besides this, I'm adding @vitoravelino as a reviewer and I'll proceed to further test this locally.

@mssola
Copy link
Collaborator

mssola commented Aug 1, 2017

You may also want to document which callback to be used for each provider. I've tested Github and Gitlab. A couple of comments:

  • Github: they don't use the same terminology (key is Client ID and secret is Client secret). You should document this as well, since for some users this might not be obvious (it is, but let's play safe...).
  • Gitlab: you should document the scopes needed for this to work. Plus, I couldn't test this with my company's Gitlab instance. Could you add support for this ?

@mssola mssola added this to the Release 2.3 milestone Aug 2, 2017
- if show_first_user_alert?
.alert.alert-info
strong Note:
| The first user to be created will have admin permissions !

- elsif APP_CONFIG["oauth"] && APP_CONFIG["oauth"].find_all {|k, v| v["enabled"]}.size > 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Extract this condition to a helper

Choose a reason for hiding this comment

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

Ok

- if show_first_user_alert?
.alert.alert-info
strong Note:
| The first user to be created will have admin permissions !

- elsif APP_CONFIG["oauth"] && APP_CONFIG["oauth"].find_all {|k, v| v["enabled"]}.size > 0
.row
Copy link
Contributor

Choose a reason for hiding this comment

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

Extract the whole social login to its own partial.

Choose a reason for hiding this comment

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

Ok

@@ -36,6 +36,20 @@ section.sign-up {
.btn-link {
color: $white
}
h1, h2, h3, h4, h5 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to change this to something like .social-login-title and add it to the html element.

Choose a reason for hiding this comment

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

Ok

margin-bottom: 5px;
border-radius: 4px;
}
p {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same apply to this. Be more specific.

Copy link
Contributor

Choose a reason for hiding this comment

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

Btw, can you tell me where this is being applied? I couldn't find it.

Choose a reason for hiding this comment

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

will remove it

h1, h2, h3, h4, h5 {
color: $white;
padding: 6px 12px;
font-family: arial;
Copy link
Contributor

Choose a reason for hiding this comment

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

No need of font-family.

Choose a reason for hiding this comment

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

Ok

@@ -36,6 +36,20 @@ section.sign-up {
.btn-link {
color: $white
}
h1, h2, h3, h4, h5 {
color: $white;
padding: 6px 12px;
Copy link
Contributor

@vitoravelino vitoravelino Aug 10, 2017

Choose a reason for hiding this comment

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

I usually prefer to use margin instead of padding for spacing titles. In this case, I would guess margin-top 30px (20px default + 10px that you were already doing with padding) and margin-bottom 15px. That's my personal preference. What do you think?

Choose a reason for hiding this comment

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

Ok

@vitoravelino
Copy link
Contributor

vitoravelino commented Aug 10, 2017

You may also want to document which callback to be used for each provider.

Agreed. 👍

@@ -7,7 +7,7 @@ index 5a5b38d20e7e..309f537a8fa2 100644

# If the deployment is done through Puma, include it in the bundle.
-gem "puma", "~> 3.7.0" if ENV["PORTUS_PUMA_DEPLOYMENT"] == "yes" || !packaging?
+gem "puma", "~> 2.16.0" if ENV["PORTUS_PUMA_DEPLOYMENT"] == "yes" || !packaging?
+gem "puma", "= 2.16.0" if ENV["PORTUS_PUMA_DEPLOYMENT"] == "yes" || !packaging?
Copy link
Contributor

Choose a reason for hiding this comment

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

@mssola Is this change necessary? Just wondering...

Choose a reason for hiding this comment

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

I don't know where it comes from. Will try to find out.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It comes from a rebase I think. I introduced this change in a commit that happened when this was being reviewed-developed.

@@ -120,7 +120,7 @@ index 329a6d22d549..d584cc4a122f 100644
pry-rails
public_activity
- puma (~> 3.7.0)
+ puma (~> 2.16.0)
+ puma (= 2.16.0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

same

= form_for @user, url: users_oauth_url do |f|
= f.text_field :username, class: 'input form-control input-lg first', placeholder: 'Username', autofocus: true, required: true
= f.text_field :display_name, class: 'input form-control input-lg last', placeholder: 'Display name'
= f.button 'Create account', class: 'classbutton btn btn-primary btn-block btn-lg'
Copy link
Contributor

Choose a reason for hiding this comment

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

Add .fa-check icon as in the standard registration page.

Choose a reason for hiding this comment

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

Ok

= image_tag 'layout/portus-logo-login-page.png', class: 'login-picture'
.row
.col-sm-12
h5.create-new-account Define your Username and Display Name
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest to change to change this to p and ignore the .col-sm-12 and .row, going right below the image_tag.

You can also associate it with a class and style it as you might need.

Choose a reason for hiding this comment

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

Ok

@vitoravelino
Copy link
Contributor

I'm not sure if this is a bug in the omniauth-github gem, but the suggested username was the beginning of my email instead of my github username.

Copy link
Contributor

@vitoravelino vitoravelino left a comment

Choose a reason for hiding this comment

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

I've made a few comments... If you have any question, please feel free to reach me out, ok? I'll be more than happy to help you with it.

Thanks a lot for the PR, a really great feature! 👍

@andrew2net
Copy link

andrew2net commented Aug 11, 2017

@vitoravelino suggesting username is not omniauth-github function. We suggest username based on email because not all providers have a username. For example, Open ID doesn't provide a username.

It was just weird to have contact appearing as the suggested username. I would prefer to leave the username empty and just the full name filled. Is that possible? What do you think, @mssola?

Of course, it possible to leave the username empty, but since the feature is @Vad1mo suggestion, I think we should wait for his comment.
May be it would better leave empty the field when a provider doesn't supply username, and fill it when username supplied.

@Vad1mo
Copy link
Contributor Author

Vad1mo commented Aug 12, 2017

Since the username can't be changed once it is set the user need to choose hist uername quite wisely as he is going to use it for each image push/pull.

In the first iteration of OAuth there wasn't even an option to set username and password. Thats why we decided to give the user the option to set its username when it is possible.

From the usability and uniformity point of view this process would fit best:

  1. OAuth provider delivers a username. Then the input field is prefilled with that username.
  2. OAuth provider does not provide username. Then the input field is prefilled with email prefix.
    In many cases this might be exactly what the user wants. Think of organisations using the registry.

Leaving the username out in the second case, is personally OK for me, however it would break the uniformity of the flow.

@Vad1mo Vad1mo force-pushed the omniauth branch 3 times, most recently from dda7f98 to cd9e640 Compare August 17, 2017 23:32
@Vad1mo
Copy link
Contributor Author

Vad1mo commented Aug 24, 2017

@mssola @vitoravelino, what changes should we implement for this PR?

github:
enabled: false
# Application credentials.
key: ""
Copy link
Contributor

Choose a reason for hiding this comment

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

As @mssola mentioned a few comments ago, it would be nice to rename this to client_id and client_secret below to avoid confusion.

# Gitlab authentication support.
gitlab:
enabled: false
id: ""
Copy link
Contributor

Choose a reason for hiding this comment

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

Here could be application_id

@vitoravelino
Copy link
Contributor

Missing space between icon and text:

screenshot 2017-09-06 14 45 12

@vitoravelino
Copy link
Contributor

vitoravelino commented Sep 6, 2017

I'm not sure if I missed this somewhere but it would be nice to have somewhere documented which callback url should be used for each service. Also, which are the minimum permissions to be selected to make the integration work (gitlab and bitbucket forces you to choose something).

Copy link
Contributor

@vitoravelino vitoravelino left a comment

Choose a reason for hiding this comment

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

After implementing those changes, it's a huge 👍 from myself.

@andrew2net
Copy link

@vitoravelino I have no image there callback URLs could be documented because it is Devise's feature. Devise makes route /users/auth/:action/callback there action is name of provider.

@vitoravelino
Copy link
Contributor

@vitoravelino I have no image there callback URLs could be documented because it is Devise's feature. Devise makes route /users/auth/:action/callback there action is name of provider.

I know. What I mean is that when we are creating the oauth application on the providers' websites (e.g.: gitlab, github, etc), we are asked about the callback redirect url. It would be nice to have somewhere (it could even be in the config.yml before each provider section), that the callback url for that provider would be <host>/users/auth/:provider/callback.

This is not required to merge the PR, this is just a nice to have thing because users might be confused when trying to configure all of this. I was confused in the beginning for example, I had to check to find something related to callback.

Copy link
Collaborator

@mssola mssola left a comment

Choose a reason for hiding this comment

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

Portus crashed on bitbucket support if the team option is left empty.

domain: ""
options:
# Only members of team can sign in/up with Bitbucket. Need permission to read team membership.
team: ""
# Set first_user_admin to true if you want that the first user that signs up
Copy link
Collaborator

Choose a reason for hiding this comment

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

Leave an empty line of separation 😉

@andrew2net
Copy link

@mssola I got error in Travis

...
patching file Gemfile
Hunk #1 succeeded at 61 (offset 4 lines).
Hunk #2 succeeded at 80 (offset 4 lines).
Hunk #3 succeeded at 97 (offset 4 lines).
patching file Gemfile.lock
Hunk #3 FAILED at 231.
Hunk #4 succeeded at 286 (offset 26 lines).
Hunk #5 FAILED at 285.
Hunk #6 succeeded at 332 (offset 29 lines).
Hunk #7 succeeded at 405 (offset 30 lines).
Hunk #8 succeeded at 511 (offset 34 lines).
2 out of 8 hunks FAILED -- saving rejects to file Gemfile.lock.rej
The command "packaging/suse/make_spec.sh portus" exited with 255.
...

could you explain what does it mean?

@mssola
Copy link
Collaborator

mssola commented Sep 7, 2017

@andrew2net feel free to ignore this error. It's due some packaging issues that I'm fixing in another PR

@andrew2net
Copy link

andrew2net commented Sep 7, 2017

@mssola for me Portus with on Bitbucket and the empty team works without errors. May I have your log?

@mssola
Copy link
Collaborator

mssola commented Sep 7, 2017

@andrew2net here it goes:

Started GET "/users/auth/bitbucket/callback?state=d7610f627c22440a22ea79f91a6a9317a9f417c203bfdcdd&code=D3ZJVMwSVVeac54HKF" for 127.0.0.1 at 2017-09-07 16:23:53 +0200
I, [2017-09-07T16:23:53.602942 #6225]  INFO -- omniauth: (bitbucket) Callback phase initiated.

Faraday::ConnectionFailed (execution expired):
  lib/omni_auth/strategies/bitbucket.rb:45:in `build_access_token_with_team_check'
  app/middleware/catch_json_parse_errors.rb:9:in `call'


  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/actionpack-4.2.8/lib/action_dispatch/middleware/templates/rescues/_source.erb (4.9ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/actionpack-4.2.8/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.9ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/actionpack-4.2.8/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.0ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/actionpack-4.2.8/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (24.0ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/_markup.html.erb (0.4ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.4ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.3ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.2ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/console.js.erb within layouts/javascript (14.3ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/main.js.erb within layouts/javascript (0.3ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.4ms)
  Rendered /home/mssola/.rvm/gems/ruby-2.3.3/gems/web-console-2.1.3/lib/web_console/templates/index.html.erb (32.3ms)

Could it be that it's because I'm using a local development instance instead of a production reachable one ?

@andrew2net
Copy link

@mssola I also using local development instance. The error comes from Fraday. Looks like it is some network issue. The piece of code related to team doesn't even start. Could you try late test both with team and without?

Signed-off-by: Vadim Bauer <bauer.vadim@gmail.com>
Signed-off-by: Vadim Bauer <bauer.vadim@gmail.com>
Copy link
Collaborator

@mssola mssola left a comment

Choose a reason for hiding this comment

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

The bitbucket failure for me is a simple networking issue. Connecting to bitbucket takes ages for me. Since @vitoravelino assures me that it works for him, I'll give my 👍

I'll fix the failures reported in Travis myself.

Copy link
Contributor

@vitoravelino vitoravelino left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@mssola mssola merged commit 0a5fefd into SUSE:master Sep 8, 2017
@mssola
Copy link
Collaborator

mssola commented Sep 8, 2017

Thanks a lot for your patience and hard work ! 🎉

@bufferoverflow
Copy link

🎉 awesome!

mssola pushed a commit to mssola/Portus that referenced this pull request Sep 12, 2017
Adding OAuth Support with omniauth

Signed-off-by: Vadim Bauer <bauer.vadim@gmail.com>
@Vad1mo Vad1mo deleted the omniauth branch February 6, 2018 21:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants