Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/graphql/types/application_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ class ApplicationType < Types::BaseObject
null: true,
description: 'URL to the legal notice page'

# rubocop:disable GraphQL/ExtractType -- one is the list and the second one is for on-demand querying
field :identity_providers, Types::IdentityProviderBasicType.connection_type,
null: false,
description: 'Configured identity providers for login and registration'

field :identity_provider_login_url, String, null: true,
description: 'Login URL for a specific identity provider' do
argument :id, String, required: true, description: 'ID of the identity provider'
end
# rubocop:enable GraphQL/ExtractType

expose_abilities %i[
create_organization
create_runtime
Expand Down Expand Up @@ -50,6 +61,17 @@ def terms_and_conditions_url
def legal_notice_url
ApplicationSetting.current[:legal_notice_url]
end

def identity_providers
ApplicationSetting.current[:identity_providers]
end

def identity_provider_login_url(id:)
provider = Users::Identity::BaseService.new.identity_provider.providers[id]
return nil if provider.nil?

provider.authorization_url
end
end
end

Expand Down
10 changes: 10 additions & 0 deletions app/graphql/types/identity_provider_basic_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module Types
class IdentityProviderBasicType < Types::BaseObject
description 'Represents a basic identity provider.'

field :id, String, null: false, description: 'Unique identifier of the identity provider.'
field :type, Types::IdentityProviderTypeEnum, null: false, description: 'Type of the identity provider.'
end
end
5 changes: 1 addition & 4 deletions app/graphql/types/identity_provider_type.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# frozen_string_literal: true

module Types
class IdentityProviderType < Types::BaseObject
class IdentityProviderType < IdentityProviderBasicType
description 'Represents an identity provider configuration.'

field :id, String, null: false, description: 'Unique identifier of the identity provider.'
field :type, Types::IdentityProviderTypeEnum, null: false, description: 'Type of the identity provider.'

field :config, Types::IdentityProviderConfigType, null: false,
description: 'Configuration details of the identity provider.'

Expand Down
13 changes: 13 additions & 0 deletions docs/graphql/object/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@ Represents the application instance
| Name | Type | Description |
|------|------|-------------|
| `currentLicense` | [`License`](../object/license.md) | (EE only) Currently active license of the instance |
| `identityProviders` | [`IdentityProviderBasicConnection!`](../object/identityproviderbasicconnection.md) | Configured identity providers for login and registration |
| `legalNoticeUrl` | [`String`](../scalar/string.md) | URL to the legal notice page |
| `licenses` | [`LicenseConnection!`](../object/licenseconnection.md) | (EE only) Licenses of the instance |
| `metadata` | [`Metadata`](../object/metadata.md) | Metadata about the application |
| `privacyUrl` | [`String`](../scalar/string.md) | URL to the privacy policy page |
| `settings` | [`ApplicationSettings`](../object/applicationsettings.md) | Global application settings |
| `termsAndConditionsUrl` | [`String`](../scalar/string.md) | URL to the terms and conditions page |
| `userAbilities` | [`ApplicationUserAbilities!`](../object/applicationuserabilities.md) | Abilities for the current user on this Application |

## Fields with arguments

### identityProviderLoginUrl

Login URL for a specific identity provider

Returns [`String`](../scalar/string.md).

| Name | Type | Description |
|------|------|-------------|
| `id` | [`String!`](../scalar/string.md) | ID of the identity provider |
12 changes: 12 additions & 0 deletions docs/graphql/object/identityproviderbasic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: IdentityProviderBasic
---

Represents a basic identity provider.

## Fields without arguments

| Name | Type | Description |
|------|------|-------------|
| `id` | [`String!`](../scalar/string.md) | Unique identifier of the identity provider. |
| `type` | [`IdentityProviderType!`](../enum/identityprovidertype.md) | Type of the identity provider. |
14 changes: 14 additions & 0 deletions docs/graphql/object/identityproviderbasicconnection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title: IdentityProviderBasicConnection
---

The connection type for IdentityProviderBasic.

## Fields without arguments

| Name | Type | Description |
|------|------|-------------|
| `count` | [`Int!`](../scalar/int.md) | Total count of collection. |
| `edges` | [`[IdentityProviderBasicEdge]`](../object/identityproviderbasicedge.md) | A list of edges. |
| `nodes` | [`[IdentityProviderBasic]`](../object/identityproviderbasic.md) | A list of nodes. |
| `pageInfo` | [`PageInfo!`](../object/pageinfo.md) | Information to aid in pagination. |
12 changes: 12 additions & 0 deletions docs/graphql/object/identityproviderbasicedge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: IdentityProviderBasicEdge
---

An edge in a connection.

## Fields without arguments

| Name | Type | Description |
|------|------|-------------|
| `cursor` | [`String!`](../scalar/string.md) | A cursor for use in pagination. |
| `node` | [`IdentityProviderBasic`](../object/identityproviderbasic.md) | The item at the end of the edge. |
2 changes: 2 additions & 0 deletions extensions/ee/spec/graphql/ee/types/application_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
legalNoticeUrl
licenses
currentLicense
identityProviders
identityProviderLoginUrl
user_abilities
]
end
Expand Down
2 changes: 2 additions & 0 deletions spec/graphql/types/application_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
privacyUrl
termsAndConditionsUrl
legalNoticeUrl
identityProviders
identityProviderLoginUrl
user_abilities
]
end
Expand Down
115 changes: 115 additions & 0 deletions spec/requests/graphql/query/identity_provider_login_url_query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'application identityProviderLoginUrl Query' do
include GraphqlHelpers

let(:query) do
<<~QUERY
query($id: String!) {
application {
identityProviderLoginUrl(id: $id)
}
}
QUERY
end

let(:current_user) { nil }
let(:variables) { { id: provider_id } }

before do
stub_application_settings(identity_providers: identity_providers)
post_graphql(query, variables: variables, current_user: current_user)
end

context 'with an OIDC provider' do
let(:provider_id) { 'my-oidc' }

let(:identity_providers) do
[
{
id: 'my-oidc',
type: 'oidc',
config: {
client_id: 'oidc-client-id',
client_secret: 'oidc-client-secret',
redirect_uri: 'https://example.com/callback',
user_details_url: 'https://idp.example.com/userinfo',
authorization_url: 'https://idp.example.com/authorize?client_id={client_id}&redirect_uri={redirect_uri}',
},
}
]
end

it 'returns the login url with substituted parameters' do
login_url = graphql_data_at(:application, :identity_provider_login_url)

expect(login_url).to eq('https://idp.example.com/authorize?client_id=oidc-client-id&redirect_uri=https://example.com/callback')
end
end

context 'with a SAML provider' do
let(:provider_id) { 'my-saml' }

let(:identity_providers) do
[
{
id: 'my-saml',
type: 'saml',
config: {
settings: {
idp_sso_service_url: 'https://idp.example.com/saml/sso',
idp_cert_fingerprint: 'AB:CD:EF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00',
sp_entity_id: 'https://example.com/saml/metadata',
},
},
}
]
end

it 'returns the login url' do
login_url = graphql_data_at(:application, :identity_provider_login_url)

expect(login_url).to be_present
expect(login_url).to include('https://idp.example.com/saml/sso?SAMLRequest=')
end
end

context 'when provider does not exist' do
let(:provider_id) { 'nonexistent' }
let(:identity_providers) { [] }

it 'returns null' do
login_url = graphql_data_at(:application, :identity_provider_login_url)

expect(login_url).to be_nil
end
end

context 'when querying without authentication' do
let(:provider_id) { 'my-oidc' }

let(:identity_providers) do
[
{
id: 'my-oidc',
type: 'oidc',
config: {
client_id: 'oidc-client-id',
client_secret: 'oidc-client-secret',
redirect_uri: 'https://example.com/callback',
user_details_url: 'https://idp.example.com/userinfo',
authorization_url: 'https://idp.example.com/authorize?client_id={client_id}&redirect_uri={redirect_uri}',
},
}
]
end

it 'returns the login url without requiring authentication' do
login_url = graphql_data_at(:application, :identity_provider_login_url)

expect(login_url).to be_present
end
end
end