Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-20 03:09:04 +00:00
parent f0e9d20cd8
commit 514ada7cc9
28 changed files with 1125 additions and 29 deletions

View File

@ -1481,6 +1481,13 @@
changes: ["vendor/gems/ipynbdiff/**/*"]
- <<: *if-merge-request-labels-run-all-rspec
.vendor:rules:omniauth_crowd:
rules:
- <<: *if-merge-request
changes: ["vendor/gems/omniauth_crowd/**/*"]
- <<: *if-merge-request-labels-run-all-rspec
.vendor:rules:omniauth-gitlab:
rules:
- <<: *if-merge-request

View File

@ -14,6 +14,14 @@ vendor ipynbdiff:
include: vendor/gems/ipynbdiff/.gitlab-ci.yml
strategy: depend
vendor omniauth_crowd:
extends:
- .vendor:rules:omniauth_crowd
needs: []
trigger:
include: vendor/gems/omniauth_crowd/.gitlab-ci.yml
strategy: depend
vendor omniauth-gitlab:
extends:
- .vendor:rules:omniauth-gitlab

View File

@ -51,7 +51,7 @@ gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.4.0'
gem 'omniauth_crowd', '~> 2.4.0', path: 'vendor/gems/omniauth_crowd' # See vendor/gems/omniauth_crowd/README.md
gem 'omniauth-authentiq', '~> 0.3.3'
gem 'gitlab-omniauth-openid-connect', '~> 0.9.0', require: 'omniauth_openid_connect'
gem 'omniauth-salesforce', '~> 1.0.5'

View File

@ -31,6 +31,14 @@ PATH
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.7.1)
PATH
remote: vendor/gems/omniauth_crowd
specs:
omniauth_crowd (2.4.0)
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0, < 3)
GEM
remote: https://rubygems.org/
specs:
@ -915,10 +923,6 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
omniauth_crowd (2.4.0)
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
open4 (1.3.4)
openid_connect (1.3.0)
activemodel
@ -1652,7 +1656,7 @@ DEPENDENCIES
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.4.0)
omniauth_crowd (~> 2.4.0)!
org-ruby (~> 0.9.12)
pact (~> 1.12)
parallel (~> 1.19)

View File

@ -6,6 +6,7 @@ module ContainerRegistry
ALLOWED_ACTIONS = %w(push delete).freeze
PUSH_ACTION = 'push'
DELETE_ACTION = 'delete'
EVENT_TRACKING_CATEGORY = 'container_registry:notification'
attr_reader :event
@ -41,6 +42,10 @@ module ContainerRegistry
event['target'].has_key?('tag')
end
def target_digest?
event['target'].has_key?('digest')
end
def target_repository?
!target_tag? && event['target'].has_key?('repository')
end
@ -53,6 +58,10 @@ module ContainerRegistry
PUSH_ACTION == action
end
def action_delete?
DELETE_ACTION == action
end
def container_repository_exists?
return unless container_registry_path
@ -74,7 +83,7 @@ module ContainerRegistry
def update_project_statistics
return unless supported?
return unless target_tag?
return unless target_tag? || (action_delete? && target_digest?)
return unless project
Rails.cache.delete(project.root_ancestor.container_repositories_size_cache_key)

View File

@ -207,7 +207,6 @@ Learn how to install, configure, update, and maintain your GitLab instance.
## Troubleshooting
- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong.
- [Log system](logs.md): Where to look for logs.
- [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs.
- [Troubleshooting Elasticsearch](troubleshooting/elasticsearch.md)

View File

@ -1,14 +1,11 @@
---
stage: Systems
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
redirect_to: '../reference_architectures/troubleshooting.md'
remove_date: '2022-10-19'
---
# Debugging tips **(FREE SELF)**
This document was moved to [another location](../reference_architectures/troubleshooting.md).
Sometimes things don't work the way they should. Here are some tips on debugging issues out
in production.
## More information
- [Debugging Stuck Ruby Processes](https://newrelic.com/blog/best-practices/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9)
<!-- This redirect file can be deleted after 2022-10-19. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -21,8 +21,10 @@ installation.
- [Linux cheat sheet](linux_cheat_sheet.md)
- [Parsing GitLab logs with `jq`](log_parsing.md)
- [Diagnostics tools](diagnostics_tools.md)
- [Debugging tips](debug.md)
- [Tracing requests with correlation ID](tracing_correlation_id.md)
Some feature documentation pages also have a troubleshooting section at the end
that you can check for feature-specific help.
If you need a testing environment to troubleshoot, see the
[apps for a testing environment](test_environments.md).

View File

@ -170,5 +170,5 @@ This parameter is used for filtering by attributes, such as `environment_scope`.
Example usage:
```shell
curl --request DELETE --globoff --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1?filter[environment_scope]=production"
curl --globoff --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1?filter[environment_scope]=production"
```

View File

@ -57,8 +57,7 @@ NOTE:
Username search is case insensitive.
In addition, you can filter users based on the states `blocked` and `active`.
It does not support `active=false` or `blocked=false`. The list of billable users
is the total number of users minus the blocked users.
It does not support `active=false` or `blocked=false`.
```plaintext
GET /users?active=true

View File

@ -40,16 +40,18 @@ RSpec.describe ContainerRegistry::Event do
subject(:handle!) { described_class.new(raw_event).handle! }
it 'enqueues a project statistics update' do
expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:container_registry_size])
shared_examples 'event with project statistics update' do
it 'enqueues a project statistics update' do
expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:container_registry_size])
handle!
end
handle!
end
it 'clears the cache for the namespace container repositories size' do
expect(Rails.cache).to receive(:delete).with(group.container_repositories_size_cache_key)
it 'clears the cache for the namespace container repositories size' do
expect(Rails.cache).to receive(:delete).with(group.container_repositories_size_cache_key)
handle!
handle!
end
end
shared_examples 'event without project statistics update' do
@ -60,10 +62,32 @@ RSpec.describe ContainerRegistry::Event do
end
end
it_behaves_like 'event with project statistics update'
context 'with no target tag' do
let(:target) { super().without('tag') }
it_behaves_like 'event without project statistics update'
context 'with a target digest' do
let(:target) { super().merge('digest' => 'abc123') }
it_behaves_like 'event without project statistics update'
end
context 'with a delete action' do
let(:action) { 'delete' }
context 'without a target digest' do
it_behaves_like 'event without project statistics update'
end
context 'with a target digest' do
let(:target) { super().merge('digest' => 'abc123') }
it_behaves_like 'event with project statistics update'
end
end
end
context 'with an unsupported action' do

View File

@ -0,0 +1,30 @@
workflow:
rules:
- if: $CI_MERGE_REQUEST_ID
.rspec:
cache:
key: omniauth-gitlab-ruby
paths:
- vendor/gems/omniauth_crowd/vendor/ruby
before_script:
- cd vendor/gems/omniauth_crowd
- ruby -v # Print out ruby version for debugging
- gem install bundler --no-document # Bundler is not installed with the image
- bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
- bundle config set with 'development'
- bundle install -j $(nproc)
script:
- bundle exec rspec
rspec-2.6:
image: "ruby:2.6"
extends: .rspec
rspec-2.7:
image: "ruby:2.7"
extends: .rspec
rspec-3.0:
image: "ruby:3.0"
extends: .rspec

4
vendor/gems/omniauth_crowd/Gemfile vendored Normal file
View File

@ -0,0 +1,4 @@
source 'http://rubygems.org'
# Specify your gem's dependencies in omniauth-github.gemspec
gemspec

74
vendor/gems/omniauth_crowd/Gemfile.lock vendored Normal file
View File

@ -0,0 +1,74 @@
PATH
remote: .
specs:
omniauth_crowd (2.4.0)
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0, < 3)
GEM
remote: http://rubygems.org/
specs:
activesupport (5.0.0.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
concurrent-ruby (1.0.5)
crack (0.4.3)
safe_yaml (~> 1.0.0)
diff-lcs (1.2.5)
hashdiff (0.3.6)
hashie (3.4.3)
i18n (0.8.1)
mini_portile2 (2.1.0)
minitest (5.10.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
omniauth (1.3.1)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
public_suffix (3.0.0)
rack (1.6.4)
rack-test (0.6.3)
rack (>= 1.0)
rake (10.5.0)
rexml (3.2.5)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.4)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.4)
rspec-support (~> 3.0.0)
rspec-support (3.0.4)
safe_yaml (1.0.4)
thread_safe (0.3.6)
tzinfo (1.2.2)
thread_safe (~> 0.1)
webmock (3.0.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
PLATFORMS
ruby
DEPENDENCIES
bundler (> 1.0.0)
omniauth_crowd!
rack
rack-test
rake
rexml (~> 3.2.5)
rspec (~> 3.0.0)
webmock (~> 3.0.0)
BUNDLED WITH
2.3.15

20
vendor/gems/omniauth_crowd/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2011 Rob Di Marco
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

52
vendor/gems/omniauth_crowd/README.md vendored Normal file
View File

@ -0,0 +1,52 @@
# omniauth_crowd
This is fork of [omniauth_crowd](https://github.com/robdimarco/omniauth_crowd) to support:
1. OmniAuth v1 and v2. OmniAuth v2 disables GET requests by default
and defaults to POST. GitLab already has patched v1 to use POST,
but other dependencies need to be updated:
https://gitlab.com/gitlab-org/gitlab/-/issues/30073.
2. We may deprecate this library entirely in the future:
https://gitlab.com/gitlab-org/gitlab/-/issues/366212
The omniauth_crowd library is an OmniAuth provider that supports authentication against Atlassian Crowd REST apis.
[![Build Status](https://travis-ci.org/robdimarco/omniauth_crowd.svg?branch=master)](https://travis-ci.org/robdimarco/omniauth_crowd)
## Helpful links
* [Documentation](http://github.com/robdimarco/omniauth_crow)
* [OmniAuth](https://github.com/intridea/omniauth/)
* [Atlassian Crowd](http://www.atlassian.com/software/crowd/)
* [Atlassian Crowd REST API](http://confluence.atlassian.com/display/CROWDDEV/Crowd+REST+APIs)
## Install and use
### 1. Add the OmniAuth Crowd REST plugin to your Gemfile
gem 'omniauth', '>= 1.0.0' # We depend on this
gem "omniauth_crowd"
### 2. You will need to configure OmniAuth to use your crowd authentication. This is generally done in Rails in the config/initializers/omniauth.rb with...
Rails.application.config.middleware.use OmniAuth::Builder do
provider :crowd, :crowd_server_url=>"https://crowd.mycompanyname.com/crowd", :application_name=>"app", :application_password=>"password"
end
You will need to supply the correct server URL, application name and password
## Contributing to omniauth_crowd
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
* Fork the project
* Start a feature/bugfix branch
* Commit and push until you are happy with your contribution
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
## Copyright
Copyright (c) 2011-14 Rob Di Marco. See LICENSE.txt for
further details.

12
vendor/gems/omniauth_crowd/Rakefile vendored Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env rake
require "bundler/gem_tasks"
require 'rspec/core/rake_task'
desc 'Default: run specs.'
task :default => :spec
desc "Run specs"
RSpec::Core::RakeTask.new
desc 'Run specs'
task :default => :spec

View File

@ -0,0 +1,97 @@
require 'omniauth'
require 'active_support'
require 'active_support/core_ext/object'
module OmniAuth
module Strategies
class Crowd
include OmniAuth::Strategy
autoload :Configuration, 'omniauth/strategies/crowd/configuration'
autoload :CrowdValidator, 'omniauth/strategies/crowd/crowd_validator'
def initialize(app, options = {}, &block)
options.symbolize_keys!()
super(app, {:name=> :crowd}.merge(options), &block)
@configuration = OmniAuth::Strategies::Crowd::Configuration.new(options)
end
protected
def request_phase
if env['REQUEST_METHOD'] == 'GET'
if @configuration.use_sessions? && request.cookies[@configuration.session_cookie]
redirect callback_url
else
get_credentials
end
elsif (env['REQUEST_METHOD'] == 'POST') && (not request.params['username'])
get_credentials
else
session['omniauth.crowd'] = {'username' => request['username'], 'password' => request['password']}
redirect callback_url
end
end
def get_client_ip
env['HTTP_X_FORWARDED_FOR'] ? env['HTTP_X_FORWARDED_FOR'] : env['REMOTE_ADDRESS']
end
def get_sso_tokens
env['HTTP_COOKIE'].split(';').select { |val|
val.strip.start_with?(@configuration.session_cookie)
}.map { |val|
val.strip.split('=').last
}
end
def get_credentials
configuration = @configuration
OmniAuth::Form.build(:title => (options[:title] || "Crowd Authentication")) do
text_field 'Login', 'username'
password_field 'Password', 'password'
if configuration.use_sessions? && configuration.sso_url
fieldset 'SSO' do
html "<a href=\"#{configuration.sso_url}/users/auth/crowd/callback\">" + (configuration.sso_url_image ? "<img src=\"#{configuration.sso_url_image}\" />" : '') + "</a>"
end
end
end.to_response
end
def callback_phase
creds = session.delete 'omniauth.crowd'
username = creds.nil? ? nil : creds['username']
password = creds.nil? ? nil : creds['password']
unless creds
if @configuration.use_sessions? && request.cookies[@configuration.session_cookie]
validator = CrowdValidator.new(@configuration, username, password, get_client_ip, get_sso_tokens)
else
return fail!(:no_credentials)
end
else
validator = CrowdValidator.new(@configuration, username, password, get_client_ip, nil)
end
@user_info = validator.user_info
return fail!(:invalid_credentials) if @user_info.nil? || @user_info.empty?
super
end
def auth_hash
OmniAuth::Utils.deep_merge(super, {
'uid' => @user_info.delete("user"),
'info' => @user_info
})
end
end
end
end

View File

@ -0,0 +1,110 @@
require 'rack'
module OmniAuth
module Strategies
class Crowd
class Configuration
DEFAULT_SESSION_URL = "%s/rest/usermanagement/latest/session"
DEFAULT_AUTHENTICATION_URL = "%s/rest/usermanagement/latest/authentication"
DEFAULT_USER_GROUP_URL = "%s/rest/usermanagement/latest/user/group/direct"
DEFAULT_CONTENT_TYPE = 'application/xml'
DEFAULT_SESSION_COOKIE = 'crowd.token_key'
attr_reader :crowd_application_name, :crowd_password, :disable_ssl_verification, :include_users_groups, :use_sessions, :session_url, :content_type, :session_cookie, :sso_url, :sso_url_image
alias :"disable_ssl_verification?" :disable_ssl_verification
alias :"include_users_groups?" :include_users_groups
alias :"use_sessions?" :use_sessions
# @param [Hash] params configuration options
# @option params [String, nil] :crowd_server_url the Crowd server root URL; probably something like
# `https://crowd.mycompany.com` or `https://crowd.mycompany.com/crowd`; optional.
# @option params [String, nil] :crowd_authentication_url (:crowd_server_url + '/rest/usermanagement/latest/authentication') the URL to which to
# use for authenication; optional if `:crowd_server_url` is specified,
# required otherwise.
# @option params [String, nil] :application_name the application name specified in Crowd for this application, required.
# @option params [String, nil] :application_password the application password specified in Crowd for this application, required.
# @option params [Boolean, nil] :disable_ssl_verification disable verification for SSL cert,
# helpful when you developing with a fake cert.
# @option params [Boolean, true] : include a list of user groups when getting information ont he user
# @option params [String, nil] :crowd_user_group_url (:crowd_server_url + '/rest/usermanagement/latest/user/group/direct') the URL to which to
# use for retrieving users groups optional if `:crowd_server_url` is specified, or if `:include_user_groups` is false
# required otherwise.
# @option params [Boolean, false] :use_sessions Use Crowd sessions. If the user logins with user and password create a new Crowd session. Update the session if only a session token is sent (Cookie name set by option session_cookie)
# @option params [String, 'crowd.token_key'] :session_cookie Session cookie name. Defaults to: 'crowd.token_key'
# @option params [String, nil] :sso_url URL of the external SSO page. If this parameter is defined the login form will have a link which will redirect to the SSO page. The SSO must return to the URL of the page using omniauth_crowd (Path portion '/users/auth/crowd/callback' is appended to the URL)
# @option params [String, nil] :sso_url_image Optional image URL to be used in SSO link in the login form
def initialize(params)
parse_params params
end
# Build a Crowd authentication URL from +username+.
#
# @param [String] username the username to validate
#
# @return [String] a URL like `https://crowd.myhost.com/crowd/rest/usermanagement/latest/authentication?username=USERNAME`
def authentication_url(username)
append_username @authentication_url, username
end
def user_group_url(username)
@user_group_url.nil? ? nil : append_username( @user_group_url, username)
end
private
def parse_params(options)
options= {:include_user_groups => true}.merge(options || {})
%w(application_name application_password).each do |opt|
raise ArgumentError.new(":#{opt} MUST be provided") if options[opt.to_sym] == ""
end
@crowd_application_name = options[:application_name]
@crowd_password = options[:application_password]
@use_sessions = options[:use_sessions]
@content_type = options[:content_type] || DEFAULT_CONTENT_TYPE
@session_cookie = options[:session_cookie] || DEFAULT_SESSION_COOKIE
@sso_url = options[:sso_url]
@sso_url_image = options[:sso_url_image]
unless options.include?(:crowd_server_url) || options.include?(:crowd_authentication_url)
raise ArgumentError.new("Either :crowd_server_url or :crowd_authentication_url MUST be provided")
end
if @use_sessions
@session_url = options[:crowd_session_url] || DEFAULT_SESSION_URL % options[:crowd_server_url]
validate_is_url 'session URL', @session_url
end
@authentication_url = options[:crowd_authentication_url] || DEFAULT_AUTHENTICATION_URL % options[:crowd_server_url]
validate_is_url 'authentication URL', @authentication_url
@disable_ssl_verification = options[:disable_ssl_verification]
@include_users_groups = options[:include_user_groups]
if @include_users_groups
@user_group_url = options[:crowd_user_group_url] || DEFAULT_USER_GROUP_URL % options[:crowd_server_url]
validate_is_url 'user group URL', @user_group_url
end
end
IS_NOT_URL_ERROR_MESSAGE = "%s is not a valid URL"
def validate_is_url(name, possibly_a_url)
url = URI.parse(possibly_a_url) rescue nil
raise ArgumentError.new(IS_NOT_URL_ERROR_MESSAGE % name) unless url.kind_of?(URI::HTTP)
end
# Adds +service+ as an URL-escaped parameter to +base+.
#
# @param [String] base the base URL
# @param [String] service the service (a.k.a. return-to) URL.
#
# @return [String] the new joined URL.
def append_username(base, username)
result = base.dup
result << (result.include?('?') ? '&' : '?')
result << 'username='
result << Rack::Utils.escape(username)
end
end
end
end
end

View File

@ -0,0 +1,186 @@
require 'nokogiri'
require 'net/http'
require 'net/https'
module OmniAuth
module Strategies
class Crowd
class CrowdValidator
AUTHENTICATION_REQUEST_BODY = "<password><value>%s</value></password>"
def initialize(configuration, username, password, client_ip, tokens)
@configuration, @username, @password, @client_ip, @tokens = configuration, username, password, client_ip, tokens
@authentiction_uri = URI.parse(@configuration.authentication_url(@username))
@session_uri = URI.parse(@configuration.session_url) if @configuration.use_sessions
end
def user_info
user_info_hash = retrieve_user_info!
if user_info_hash && @configuration.include_users_groups?
user_info_hash = add_user_groups!(user_info_hash)
else
user_info_hash
end
if user_info_hash && @configuration.use_sessions?
user_info_hash = set_session!(user_info_hash)
end
user_info_hash
end
private
def set_session!(user_info_hash)
response = nil
if user_info_hash["sso_token"]
response = make_session_request(user_info_hash["sso_token"])
else
response = make_session_request(nil)
end
if response.kind_of?(Net::HTTPSuccess) && response.body
doc = Nokogiri::XML(response.body)
user_info_hash["sso_token"] = doc.xpath('//token/text()').to_s
else
OmniAuth.logger.send(:warn, "(crowd) [set_session!] response code: #{response.code.to_s}")
OmniAuth.logger.send(:warn, "(crowd) [set_session!] response body: #{response.body}")
end
user_info_hash
end
def add_user_groups!(user_info_hash)
response = make_user_group_request(user_info_hash['user'])
unless response.code.to_i != 200 || response.body.nil? || response.body == ''
doc = Nokogiri::XML(response.body)
user_info_hash["groups"] = doc.xpath("//groups/group/@name").map(&:to_s)
end
user_info_hash
end
def retrieve_user_info!
response = make_authorization_request
unless response === nil
unless response.code.to_i != 200 || response.body.nil? || response.body == ''
doc = Nokogiri::XML(response.body)
result = {
"user" => doc.xpath("//user/@name").to_s,
"name" => doc.xpath("//user/display-name/text()").to_s,
"first_name" => doc.xpath("//user/first-name/text()").to_s,
"last_name" => doc.xpath("//user/last-name/text()").to_s,
"email" => doc.xpath("//user/email/text()").to_s
}
if doc.at_xpath("//token")
result["sso_token"] = doc.xpath("//token/text()").to_s
end
result
else
OmniAuth.logger.send(:warn, "(crowd) [retrieve_user_info!] response code: #{response.code.to_s}")
OmniAuth.logger.send(:warn, "(crowd) [retrieve_user_info!] response body: #{response.body}")
nil
end
else
OmniAuth.logger.send(:warn, "(crowd) [retrieve_user_info!] None of the session tokens were valid")
nil
end
end
def make_request(uri, body=nil)
http_method = body.nil? ? Net::HTTP::Get : Net::HTTP::Post
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? && @configuration.disable_ssl_verification?
http.start do |c|
req = http_method.new(uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}")
req.body = body if body
req.basic_auth @configuration.crowd_application_name, @configuration.crowd_password
if @configuration.content_type
req.add_field 'Content-Type', @configuration.content_type
end
http.request(req)
end
end
def make_user_group_request(username)
make_request(URI.parse(@configuration.user_group_url(username)))
end
def make_authorization_request
if @configuration.use_sessions? && @tokens.kind_of?(Array)
make_session_retrieval_request
else
make_request(@authentiction_uri, make_authentication_request_body(@password))
end
end
def make_session_request(token)
root = url = validation_factor = nil
doc = Nokogiri::XML::Document.new
if token === nil
url = @session_uri
root = doc.create_element('authentication-context')
doc.root = root
root.add_child(doc.create_element('username', @username))
root.add_child(doc.create_element('password', @password))
else
url = URI.parse(@session_uri.to_s() + "/#{token}")
end
if @configuration.use_sessions? || @client_ip
if root === nil
root = doc.create_element('validation-factors')
doc.root = root
else
root.add_child(doc.create_element('validation-factors'))
end
validation_factor = doc.create_element('validation-factor')
validation_factor.add_child(doc.create_element('name', 'remote_address'))
validation_factor.add_child(doc.create_element('value', @client_ip))
doc.xpath('//validation-factors').first.add_child(validation_factor)
end
make_request(url, doc.to_s)
end
# create the body using Nokogiri so proper encoding of passwords can be ensured
def make_authentication_request_body(password)
request_body = Nokogiri::XML(AUTHENTICATION_REQUEST_BODY)
password_value = request_body.at_css "value"
password_value.content = password
return request_body.root.to_s # return the body without the xml header
end
def make_session_retrieval_request
response = nil
@tokens.any? { |token|
response = make_request(URI.parse(@session_uri.to_s() + "/#{token}"))
response.code.to_i == 200 && !response.body.nil? && response.body != ''
}
response
end
end
end
end
end

View File

@ -0,0 +1 @@
require 'omniauth/strategies/crowd'

View File

@ -0,0 +1,5 @@
module OmniAuth
module Crowd
VERSION = "2.4.0"
end
end

View File

@ -0,0 +1,28 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/omniauth_crowd/version', __FILE__)
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |gem|
gem.authors = ["Robert Di Marco"]
gem.email = ["rob@innovationontherun.com"]
gem.description = "This is an OmniAuth provider for Atlassian Crowd's REST API. It allows you to easily integrate your Rack application in with Atlassian Crowd."
gem.summary = "An OmniAuth provider for Atlassian Crowd REST API"
gem.homepage = "http://github.com/robdimarco/omniauth_crowd"
gem.files = Dir.glob("lib/**/*.*")
gem.test_files = Dir.glob("spec/**/**/*.*")
gem.name = "omniauth_crowd"
gem.require_paths = ["lib"]
gem.version = OmniAuth::Crowd::VERSION
gem.add_runtime_dependency 'omniauth', '~> 1.0', '< 3'
gem.add_runtime_dependency 'nokogiri', '>= 1.4.4'
gem.add_runtime_dependency 'activesupport', '>= 0'
gem.add_development_dependency(%q<rack>, [">= 0"])
gem.add_development_dependency(%q<rake>, [">= 0"])
gem.add_development_dependency(%q<rack-test>, [">= 0"])
gem.add_development_dependency(%q<rexml>, ["~> 3.2.5"])
gem.add_development_dependency(%q<rspec>, ["~> 3.0.0"])
gem.add_development_dependency(%q<webmock>, ["~> 3.0.0"])
gem.add_development_dependency(%q<bundler>, ["> 1.0.0"])
end

View File

@ -0,0 +1,8 @@
<groups expand="group">
<group name="Developers">
<link rel="self" href="http://crowd.bogus.com/crowd/rest/usermanagement/latest/group?groupname=Developers"/>
</group>
<group name="jira-users">
<link rel="self" href="http://crowd.bogus.com/crowd/rest/usermanagement/latest/group?groupname=jira-users"/>
</group>
</groups>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<session expand="user">
<token>rtk8eMvqq00EiGn5iJCMZQ00</token>
<user name="foo">
<link rel="self" href="http://crowd.example.org/crowd/rest/usermanagement/latest/user?username=foo"/>
</user>
<link rel="self" href="http://crowd.example.org/crowd/rest/usermanagement/latest/session/rtk8eMvqq00EiGn5iJCMZQ00"/>
</session>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user name="foo" expand="attributes">
<link rel="self" href="http://crowd.example.org/crowd/rest/usermanagement/latest/user?username=foo"/>
<first-name>Foo</first-name>
<last-name>Foobaz</last-name>
<display-name>Foo Foobaz</display-name>
<email>foo@example.org</email>
<password><link rel="edit" href="http://crowd.example.org/crowd/rest/usermanagement/latest/user/password?username=foo"/></password>
<active>true</active>
<attributes><link rel="self" href="http://crowd.example.org/crowd/rest/usermanagement/latest/user/attribute?username=foo"/></attributes>
</user>

View File

@ -0,0 +1,387 @@
require 'spec_helper'
describe OmniAuth::Strategies::Crowd, :type=>:strategy do
include OmniAuth::Test::StrategyTestCase
def strategy
@crowd_server_url ||= 'https://crowd.example.org'
@application_name ||= 'bogus_app'
@application_password ||= 'bogus_app_password'
[OmniAuth::Strategies::Crowd, {:crowd_server_url => @crowd_server_url,
:application_name => @application_name,
:application_password => @application_password,
:use_sessions => @using_sessions,
:sso_url => @sso_url,
:sso_url_image => @sso_url_image
}]
end
@using_sessions = false
@sso_url = nil
@sso_url_image = nil
let(:config) { OmniAuth::Strategies::Crowd::Configuration.new(strategy[1]) }
let(:validator) { OmniAuth::Strategies::Crowd::CrowdValidator.new(config, 'foo', 'bar', nil, nil) }
describe 'Authentication Request Body' do
it 'should send password in session request' do
body = <<-BODY.strip
<password>
<value>bar</value>
</password>
BODY
expect(validator.send(:make_authentication_request_body, 'bar')).to eq(body)
end
it 'should escape special characters username and password in session request' do
body = <<-BODY.strip
<password>
<value>bar&lt;</value>
</password>
BODY
expect(validator.send(:make_authentication_request_body, 'bar<')).to eq(body)
end
end
describe 'GET /auth/crowd' do
it 'should show the login form' do
get '/auth/crowd'
expect(last_response).to be_ok
end
end
describe 'POST /auth/crowd' do
it 'should redirect to callback' do
post '/auth/crowd', :username=>'foo', :password=>'bar'
expect(last_response).to be_redirect
expect(last_response.headers['Location']).to eq('http://example.org/auth/crowd/callback')
end
end
describe 'GET /auth/crowd/callback without any credentials' do
it 'should fail' do
get '/auth/crowd/callback'
expect(last_response).to be_redirect
expect(last_response.headers['Location']).to match(/no_credentials/)
end
end
describe 'GET /auth/crowd/callback with credentials can be successful' do
context "when using authentication endpoint" do
before do
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/authentication?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'success.xml')))
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/user/group/direct?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'groups.xml')))
#Adding this to prevent Content-Type text/xml from being added back in the future
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/user/group/direct?username=foo").with(:headers => {"Content-Type" => "text/xml"}).
to_return(:status => [415, "Unsupported Media Type"])
get '/auth/crowd/callback', nil, 'rack.session'=>{'omniauth.crowd'=> {"username"=>"foo", "password"=>"ba"}}
end
it 'should call through to the master app' do
expect(last_response.body).to eq('true')
end
it 'should have an auth hash' do
auth = last_request.env['omniauth.auth']
expect(auth).to be_kind_of(Hash)
end
it 'should have good data' do
auth = last_request.env['omniauth.auth']
expect(auth['provider']).to eq(:crowd)
expect(auth['uid']).to eq('foo')
expect(auth['info']).to be_kind_of(Hash)
expect(auth['info']['groups'].sort).to eq(["Developers", "jira-users"].sort)
end
end
describe "when using session endpoint" do
before do
@using_sessions = true
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/authentication?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'success.xml')))
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/session").
to_return(:status => 201, :body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'session.xml')))
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/user/group/direct?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'groups.xml')))
end
after { @using_sessions = false }
it 'should call through to the master app' do
get '/auth/crowd/callback', nil, 'rack.session'=>{'omniauth.crowd'=> {"username"=>"foo", "password"=>"ba"}}
expect(last_response.body).to eq('true')
end
it 'should have an auth hash' do
get '/auth/crowd/callback', nil, 'rack.session'=>{'omniauth.crowd'=> {"username"=>"foo", "password"=>"ba"}}
expect(last_request.env['omniauth.auth']).to be_kind_of(Hash)
end
it 'should have good data' do
get '/auth/crowd/callback', nil, 'rack.session'=>{'omniauth.crowd'=> {"username"=>"foo", "password"=>"ba"}}
auth = last_request.env['omniauth.auth']
expect(auth['provider']).to eq(:crowd)
expect(auth['uid']).to eq('foo')
expect(auth['info']).to be_kind_of(Hash)
expect(auth['info']['sso_token']).to eq('rtk8eMvqq00EiGn5iJCMZQ00')
expect(auth['info']['groups'].sort).to eq(["Developers", "jira-users"].sort)
end
end
end
describe 'GET /auth/crowd/callback with credentials will fail' do
before do
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/authentication?username=foo").
to_return(:status=>400)
get '/auth/crowd/callback', nil, 'rack.session'=>{'omniauth.crowd'=> {"username"=>"foo", "password"=>"ba"}}
end
it 'should fail' do
expect(last_response).to be_redirect
expect(last_response.headers['Location']).to match(/invalid_credentials/)
end
end
describe 'GET /auth/crowd without credentials will redirect to login form' do
sso_url = 'https://foo.bar'
before do
@using_sessions = true
@sso_url = sso_url
end
it 'should have the SSO button in the response body' do
found_legend = found_anchor = nil
get '/auth/crowd'
Nokogiri::HTML(last_response.body).xpath('//html/body/form/fieldset/*').each do |element|
if element.name === 'legend' && element.content() === 'SSO'
found_legend = true
elsif element.name === 'a' && element.attr('href') === "#{sso_url}/users/auth/crowd/callback"
found_anchor = true
end
end
expect(found_legend).to(be(true))
expect(found_anchor).to(be(true))
end
after do
@using_sessions = false
@sso_url = nil
end
end
describe 'GET /auth/crowd without credentials will redirect to login form which has custom image in the SSO link' do
sso_url = 'https://foo.bar'
sso_url_image = 'https://foo.bar/image.png'
before do
@using_sessions = true
@sso_url = sso_url
@sso_url_image = 'https://foo.bar/image.png'
end
it 'should have the SSO button with a custom image in the response body' do
found_legend = found_anchor = found_image = false
get '/auth/crowd'
Nokogiri::HTML(last_response.body).xpath('//html/body/form/fieldset/*').each do |element|
if element.name === 'legend' && element.content() === 'SSO'
found_legend = true
elsif element.name === 'a' && element.attr('href') === "#{sso_url}/users/auth/crowd/callback"
found_anchor = true
if element.children.length === 1 && element.children.first.name === 'img' && element.children.first.attr('src') === sso_url_image
found_image = true
end
end
end
expect(found_legend).to(be(true))
expect(found_anchor).to(be(true))
expect(found_image).to(be(true))
end
after do
@using_sessions = false
@sso_url = nil
@sso_url_image = nil
end
end
describe 'GET /auth/crowd without credentials but with SSO cookie will redirect to callback' do
sso_url = 'https://foo.bar'
before do
@using_sessions = true
@sso_url = sso_url
set_cookie('crowd.token_key=foobar')
end
it 'should redirect to callback' do
get '/auth/crowd'
expect(last_response).to be_redirect
expect(last_response.headers['Location']).to eq('http://example.org/auth/crowd/callback')
end
after do
@using_sessions = false
@sso_url = nil
clear_cookies()
end
end
describe 'POST /auth/crowd/callback without credentials but with SSO cookie will redirect to login form because session is invalid' do
sso_url = 'https://foo.bar'
token = 'foobar'
before do
@using_sessions = true
@sso_url = sso_url
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/session/#{token}").
to_return(:status => [404])
set_cookie("crowd.token_key=#{token}")
end
it 'should redirect to login form' do
post '/auth/crowd/callback'
expect(last_response).to be_redirect
expect(last_response.headers['Location']).to match(/invalid_credentials/)
end
after do
@using_sessions = false
@sso_url = nil
clear_cookies()
end
end
describe 'GET /auth/crowd/callback without credentials but with SSO cookie will succeed' do
sso_url = 'https://foo.bar'
token = 'rtk8eMvqq00EiGn5iJCMZQ00'
before do
@using_sessions = true
@sso_url = sso_url
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/session/#{token}").
to_return(:status => 200, :body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'session.xml')))
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/session/#{token}").
to_return(:status => 200)
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/user/group/direct?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'groups.xml')))
set_cookie("crowd.token_key=#{token}")
end
it 'should return user data' do
auth = nil
get '/auth/crowd/callback'
auth = last_request.env['omniauth.auth']
expect(auth['provider']).to eq(:crowd)
expect(auth['uid']).to eq('foo')
expect(auth['info']).to be_kind_of(Hash)
expect(auth['info']['groups'].sort).to eq(["Developers", "jira-users"].sort)
end
after do
@using_sessions = false
@sso_url = nil
clear_cookies()
end
end
describe 'GET /auth/crowd/callback without credentials but with multiple SSO cookies will succeed because one of them is valid' do
sso_url = 'https://foo.bar'
before do
@using_sessions = true
@sso_url = sso_url
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/session/foo").
to_return(:status => 404)
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/session/fubar").
to_return(:status => 404)
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/session/rtk8eMvqq00EiGn5iJCMZQ00").
to_return(:status => 200, :body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'session.xml')))
stub_request(:post, "https://crowd.example.org/rest/usermanagement/latest/session/rtk8eMvqq00EiGn5iJCMZQ00").
to_return(:status => 200)
stub_request(:get, "https://crowd.example.org/rest/usermanagement/latest/user/group/direct?username=foo").
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'groups.xml')))
header('Cookie', "crowd.token_key=foo;crowd.token_key=rtk8eMvqq00EiGn5iJCMZQ00;crowd.token_key=fubar")
end
it 'should return user data' do
auth = nil
get '/auth/crowd/callback'
auth = last_request.env['omniauth.auth']
expect(auth['provider']).to eq(:crowd)
expect(auth['uid']).to eq('foo')
expect(auth['info']).to be_kind_of(Hash)
expect(auth['info']['groups'].sort).to eq(["Developers", "jira-users"].sort)
end
after do
@using_sessions = false
@sso_url = nil
header('Cookie', nil)
end
end
end

View File

@ -0,0 +1,14 @@
require 'bundler/setup'
Bundler.setup
require 'rack/test'
require 'webmock'
require 'webmock/rspec'
require 'nokogiri'
require 'omniauth_crowd'
RSpec.configure do |config|
WebMock.disable_net_connect!
config.include Rack::Test::Methods
config.raise_errors_for_deprecations!
end