Skip to content

Commit d48d9cf

Browse files
committed
Add query for identity providers and login urls
1 parent af5cdff commit d48d9cf

9 files changed

Lines changed: 201 additions & 4 deletions

app/graphql/types/application_type.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ class ApplicationType < Types::BaseObject
2222
null: true,
2323
description: 'URL to the legal notice page'
2424

25+
# rubocop:disable GraphQL/ExtractType -- one is the list and the second one is for on-demand querying
26+
field :identity_providers, Types::IdentityProviderBasicType.connection_type,
27+
null: false,
28+
description: 'Configured identity providers for login and registration'
29+
30+
field :identity_provider_login_url, String, null: true,
31+
description: 'Login URL for a specific identity provider' do
32+
argument :id, String, required: true, description: 'ID of the identity provider'
33+
end
34+
# rubocop:enable GraphQL/ExtractType
35+
2536
expose_abilities %i[
2637
create_organization
2738
create_runtime
@@ -50,6 +61,17 @@ def terms_and_conditions_url
5061
def legal_notice_url
5162
ApplicationSetting.current[:legal_notice_url]
5263
end
64+
65+
def identity_providers
66+
ApplicationSetting.current[:identity_providers]
67+
end
68+
69+
def identity_provider_login_url(id:)
70+
provider = Users::Identity::BaseService.new.identity_provider.providers[id]
71+
return nil if provider.nil?
72+
73+
provider.authorization_url
74+
end
5375
end
5476
end
5577

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
module Types
4+
class IdentityProviderBasicType < Types::BaseObject
5+
description 'Represents a basic identity provider.'
6+
7+
field :id, String, null: false, description: 'Unique identifier of the identity provider.'
8+
field :type, Types::IdentityProviderTypeEnum, null: false, description: 'Type of the identity provider.'
9+
end
10+
end

app/graphql/types/identity_provider_type.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
# frozen_string_literal: true
22

33
module Types
4-
class IdentityProviderType < Types::BaseObject
4+
class IdentityProviderType < IdentityProviderBasicType
55
description 'Represents an identity provider configuration.'
66

7-
field :id, String, null: false, description: 'Unique identifier of the identity provider.'
8-
field :type, Types::IdentityProviderTypeEnum, null: false, description: 'Type of the identity provider.'
9-
107
field :config, Types::IdentityProviderConfigType, null: false,
118
description: 'Configuration details of the identity provider.'
129

docs/graphql/object/application.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,23 @@ Represents the application instance
99
| Name | Type | Description |
1010
|------|------|-------------|
1111
| `currentLicense` | [`License`](../object/license.md) | (EE only) Currently active license of the instance |
12+
| `identityProviders` | [`IdentityProviderBasicConnection!`](../object/identityproviderbasicconnection.md) | Configured identity providers for login and registration |
1213
| `legalNoticeUrl` | [`String`](../scalar/string.md) | URL to the legal notice page |
1314
| `licenses` | [`LicenseConnection!`](../object/licenseconnection.md) | (EE only) Licenses of the instance |
1415
| `metadata` | [`Metadata`](../object/metadata.md) | Metadata about the application |
1516
| `privacyUrl` | [`String`](../scalar/string.md) | URL to the privacy policy page |
1617
| `settings` | [`ApplicationSettings`](../object/applicationsettings.md) | Global application settings |
1718
| `termsAndConditionsUrl` | [`String`](../scalar/string.md) | URL to the terms and conditions page |
1819
| `userAbilities` | [`ApplicationUserAbilities!`](../object/applicationuserabilities.md) | Abilities for the current user on this Application |
20+
21+
## Fields with arguments
22+
23+
### identityProviderLoginUrl
24+
25+
Login URL for a specific identity provider
26+
27+
Returns [`String`](../scalar/string.md).
28+
29+
| Name | Type | Description |
30+
|------|------|-------------|
31+
| `id` | [`String!`](../scalar/string.md) | ID of the identity provider |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: IdentityProviderBasic
3+
---
4+
5+
Represents a basic identity provider.
6+
7+
## Fields without arguments
8+
9+
| Name | Type | Description |
10+
|------|------|-------------|
11+
| `id` | [`String!`](../scalar/string.md) | Unique identifier of the identity provider. |
12+
| `type` | [`IdentityProviderType!`](../enum/identityprovidertype.md) | Type of the identity provider. |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: IdentityProviderBasicConnection
3+
---
4+
5+
The connection type for IdentityProviderBasic.
6+
7+
## Fields without arguments
8+
9+
| Name | Type | Description |
10+
|------|------|-------------|
11+
| `count` | [`Int!`](../scalar/int.md) | Total count of collection. |
12+
| `edges` | [`[IdentityProviderBasicEdge]`](../object/identityproviderbasicedge.md) | A list of edges. |
13+
| `nodes` | [`[IdentityProviderBasic]`](../object/identityproviderbasic.md) | A list of nodes. |
14+
| `pageInfo` | [`PageInfo!`](../object/pageinfo.md) | Information to aid in pagination. |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: IdentityProviderBasicEdge
3+
---
4+
5+
An edge in a connection.
6+
7+
## Fields without arguments
8+
9+
| Name | Type | Description |
10+
|------|------|-------------|
11+
| `cursor` | [`String!`](../scalar/string.md) | A cursor for use in pagination. |
12+
| `node` | [`IdentityProviderBasic`](../object/identityproviderbasic.md) | The item at the end of the edge. |

spec/graphql/types/application_type_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
privacyUrl
1111
termsAndConditionsUrl
1212
legalNoticeUrl
13+
identityProviders
14+
identityProviderLoginUrl
1315
user_abilities
1416
]
1517
end
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'application identityProviderLoginUrl Query' do
6+
include GraphqlHelpers
7+
8+
let(:query) do
9+
<<~QUERY
10+
query($id: String!) {
11+
application {
12+
identityProviderLoginUrl(id: $id)
13+
}
14+
}
15+
QUERY
16+
end
17+
18+
let(:current_user) { nil }
19+
let(:variables) { { id: provider_id } }
20+
21+
before do
22+
stub_application_settings(identity_providers: identity_providers)
23+
post_graphql(query, variables: variables, current_user: current_user)
24+
end
25+
26+
context 'with an OIDC provider' do
27+
let(:provider_id) { 'my-oidc' }
28+
29+
let(:identity_providers) do
30+
[
31+
{
32+
id: 'my-oidc',
33+
type: 'oidc',
34+
config: {
35+
client_id: 'oidc-client-id',
36+
client_secret: 'oidc-client-secret',
37+
redirect_uri: 'https://example.com/callback',
38+
user_details_url: 'https://idp.example.com/userinfo',
39+
authorization_url: 'https://idp.example.com/authorize?client_id={client_id}&redirect_uri={redirect_uri}',
40+
},
41+
}
42+
]
43+
end
44+
45+
it 'returns the login url with substituted parameters' do
46+
login_url = graphql_data_at(:application, :identity_provider_login_url)
47+
48+
expect(login_url).to eq('https://idp.example.com/authorize?client_id=oidc-client-id&redirect_uri=https://example.com/callback')
49+
end
50+
end
51+
52+
context 'with a SAML provider' do
53+
let(:provider_id) { 'my-saml' }
54+
55+
let(:identity_providers) do
56+
[
57+
{
58+
id: 'my-saml',
59+
type: 'saml',
60+
config: {
61+
settings: {
62+
idp_sso_service_url: 'https://idp.example.com/saml/sso',
63+
idp_cert_fingerprint: 'AB:CD:EF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00',
64+
sp_entity_id: 'https://example.com/saml/metadata',
65+
},
66+
},
67+
}
68+
]
69+
end
70+
71+
it 'returns the login url' do
72+
login_url = graphql_data_at(:application, :identity_provider_login_url)
73+
74+
expect(login_url).to be_present
75+
expect(login_url).to include('https://idp.example.com/saml/sso?SAMLRequest=')
76+
end
77+
end
78+
79+
context 'when provider does not exist' do
80+
let(:provider_id) { 'nonexistent' }
81+
let(:identity_providers) { [] }
82+
83+
it 'returns null' do
84+
login_url = graphql_data_at(:application, :identity_provider_login_url)
85+
86+
expect(login_url).to be_nil
87+
end
88+
end
89+
90+
context 'when querying without authentication' do
91+
let(:provider_id) { 'my-oidc' }
92+
93+
let(:identity_providers) do
94+
[
95+
{
96+
id: 'my-oidc',
97+
type: 'oidc',
98+
config: {
99+
client_id: 'oidc-client-id',
100+
client_secret: 'oidc-client-secret',
101+
redirect_uri: 'https://example.com/callback',
102+
user_details_url: 'https://idp.example.com/userinfo',
103+
authorization_url: 'https://idp.example.com/authorize?client_id={client_id}&redirect_uri={redirect_uri}',
104+
},
105+
}
106+
]
107+
end
108+
109+
it 'returns the login url without requiring authentication' do
110+
login_url = graphql_data_at(:application, :identity_provider_login_url)
111+
112+
expect(login_url).to be_present
113+
end
114+
end
115+
end

0 commit comments

Comments
 (0)