Skip to content
Open
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
2 changes: 1 addition & 1 deletion client/src/components/Login/LoginForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("LoginForm", () => {
axiosMock = new MockAdapter(axios);
server.use(
http.get("/api/configuration", ({ response }) => {
return response.untyped(HttpResponse.json({ oidc: { cilogon: false, custos: false } }));
return response.untyped(HttpResponse.json({ oidc: { cilogon: false } }));
}),
);
});
Expand Down
16 changes: 8 additions & 8 deletions client/src/components/Register/RegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ interface Props {
enableOidc?: boolean;
mailingJoinAddr?: string;
oidcIdps?: OIDCConfig;
preferCustosLogin?: boolean;
preferOidcLogin?: boolean;
redirect?: string;
registrationWarningMessage?: string;
serverMailConfigured?: boolean;
Expand All @@ -58,8 +58,8 @@ const labelSubscribe = ref(localize("Stay in the loop and join the galaxy-announ

const idpsWithRegistration = computed(() => (props.oidcIdps ? getOIDCIdpsWithRegistration(props.oidcIdps) : {}));

const custosPreferred = computed(() => {
return props.enableOidc && props.preferCustosLogin;
const oidcPreferred = computed(() => {
return props.enableOidc && props.preferOidcLogin;
});

/** This decides if all register options should be displayed in column style
Expand Down Expand Up @@ -107,30 +107,30 @@ async function submit() {

<BForm id="registration" @submit.prevent="submit()">
<BCard no-body>
<!-- OIDC and Custos enabled and prioritized: encourage users to use it instead of local registration -->
<span v-if="custosPreferred">
<!-- OIDC enabled and prioritized: encourage users to use it instead of local registration -->
<span v-if="oidcPreferred">
<BCardHeader v-b-toggle.accordion-oidc role="button">
Register using institutional account
</BCardHeader>

<BCollapse id="accordion-oidc" visible role="tabpanel" accordion="registration_acc">
<BCardBody>
Create a Galaxy account using an institutional account (e.g.:Google/JHU). This will
redirect you to your institutional login through Custos.
redirect you to your institutional login through OIDC.
<ExternalLogin class="mt-2" />
</BCardBody>
</BCollapse>
</span>

<!-- Local Galaxy Registration -->
<BCardHeader v-if="!custosPreferred" v-localize>Create a Galaxy account</BCardHeader>
<BCardHeader v-if="!oidcPreferred" v-localize>Create a Galaxy account</BCardHeader>
<BCardHeader v-else v-localize v-b-toggle.accordion-register role="button">
Or, register with email
</BCardHeader>

<BCollapse
id="accordion-register"
:visible="!custosPreferred"
:visible="!oidcPreferred"
role="tabpanel"
accordion="registration_acc">
<BCardBody :class="{ 'd-flex w-100': !registerColumnDisplay }">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type OIDCConfigWithRegistration = Record<

/** Return the per-IDP config, minus anything the caller wants to hide. */
export function getFilteredOIDCIdps(oidcConfig: OIDCConfig, exclude: string[] = []): OIDCConfig {
const blacklist = new Set(["cilogon", "custos", ...exclude]);
const blacklist = new Set(["cilogon", ...exclude]);
const filtered: OIDCConfig = {};
Object.entries(oidcConfig).forEach(([idp, cfg]) => {
if (!blacklist.has(idp)) {
Expand All @@ -51,11 +51,11 @@ export function getOIDCIdpsWithRegistration(oidcConfig: OIDCConfig): OIDCConfigW

/** Do we need to show the institution picker at all? */
export const getNeedShowCilogonInstitutionList = (cfg: OIDCConfig): boolean => {
return Boolean(cfg.cilogon || cfg.custos);
return Boolean(cfg.cilogon);
};

/**
* Generic OIDC login (all providers *except* CILogon/Custos).
* Generic OIDC login (all providers *except* CILogon).
* Returns the redirect URI Galaxy gives back, or throws.
*/
export async function submitOIDCLogon(idp: string, redirectParam: string | null = null): Promise<string | null> {
Expand All @@ -73,8 +73,8 @@ export async function submitOIDCLogon(idp: string, redirectParam: string | null
}

/**
* CILogon/Custos login.
* @param idp "cilogon" | "custos"
* CILogon login.
* @param idp "cilogon"
* @param useIDPHint If true, append ?idphint=
* @param idpHint The entityID to hint with (ignored when useIDPHint = false)
*/
Expand Down Expand Up @@ -108,7 +108,7 @@ export async function redirectToSingleProvider(config: OIDCConfig): Promise<stri
throw new Error("OIDC provider key is undefined.");
}

if (idp === "cilogon" || idp === "custos") {
if (idp === "cilogon") {
const redirectUri = await submitCILogon(idp, false);
return redirectUri;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ export default {
doomedItem: null,
errorMessage: null,
enable_oidc: galaxy.config.enable_oidc,
cilogonOrCustos: null,
userEmail: galaxy.user.get("email"),
};
},
Expand Down
40 changes: 12 additions & 28 deletions client/src/components/User/ExternalIdentities/ExternalLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const messageVariant = ref<string | null>(null);
const cILogonIdps = ref<Idp[]>([]);
const selected = ref<Idp | null>(null);
const rememberIdp = ref(false);
const cilogonOrCustos = ref<"cilogon" | "custos" | null>(null);
const cilogon = ref<"cilogon" | null>(null);
const toggleCilogon = ref(false);

const oIDCIdps = computed<OIDCConfig>(() => (isConfigLoaded.value ? config.value.oidc : {}));
Expand All @@ -60,22 +60,21 @@ const filteredOIDCIdps = computed(() => getFilteredOIDCIdps(oIDCIdps.value, prop
const cilogonListShow = computed(() => getNeedShowCilogonInstitutionList(oIDCIdps.value));

const cILogonEnabled = computed(() => oIDCIdps.value.cilogon);
const custosEnabled = computed(() => oIDCIdps.value.custos);

onMounted(async () => {
rememberIdp.value = getIdpPreference() !== null;

// Only fetch CILogonIDPs if custos/cilogon configured
// Only fetch CILogonIDPs if cilogon configured
if (cilogonListShow.value) {
await getCILogonIdps();
}
});

function toggleCILogon(idp: "cilogon" | "custos") {
if (cilogonOrCustos.value === idp || cilogonOrCustos.value === null) {
function toggleCILogon(idp: "cilogon") {
if (cilogon.value === idp || cilogon.value === null) {
toggleCilogon.value = !toggleCilogon.value;
}
cilogonOrCustos.value = toggleCilogon.value ? idp : null;
cilogon.value = toggleCilogon.value ? idp : null;
}

async function clickOIDCLogin(idp: string) {
Expand Down Expand Up @@ -186,7 +185,7 @@ function getIdpPreference() {
<!-- OIDC login-->
<BForm v-if="cilogonListShow" id="externalLogin" class="cilogon">
<div v-if="props.loginPage">
<!--Only Display if CILogon/Custos is configured-->
<!--Only Display if CILogon is configured-->
<BFormGroup label="Use existing institutional login">
<Multiselect
v-model="selected"
Expand All @@ -212,31 +211,16 @@ function getIdpPreference() {
<LoadingSpan v-if="loading" message="Signing In" />
<span v-else>Sign in with Institutional Credentials*</span>
</GButton>
<!--convert to v-else-if to allow only one or the other. if both enabled, put the one that should be default first-->
<GButton
v-if="Object.prototype.hasOwnProperty.call(oIDCIdps, 'custos')"
:disabled="loading || selected === null"
@click="clickCILogin('custos')">
<LoadingSpan v-if="loading" message="Signing In" />
<span v-else>Sign in with Custos*</span>
</GButton>
</div>

<div v-else>
<GButtonGroup class="w-100">
<GButton
v-if="cILogonEnabled"
:pressed="cilogonOrCustos === 'cilogon'"
:pressed="cilogon === 'cilogon'"
@click="toggleCILogon('cilogon')">
Sign in with Institutional Credentials*
</GButton>

<GButton
v-if="custosEnabled"
:pressed="cilogonOrCustos === 'custos'"
@click="toggleCILogon('custos')">
Sign in with Custos*
</GButton>
</GButtonGroup>

<BFormGroup v-if="toggleCilogon" class="mt-1">
Expand All @@ -254,18 +238,18 @@ function getIdpPreference() {
v-if="toggleCilogon"
class="mt-1"
:disabled="loading || selected === null"
@click="clickCILogin(cilogonOrCustos)">
Login via {{ cilogonOrCustos === "cilogon" ? "CILogon" : "Custos" }} *
@click="clickCILogin(cilogon)">
Login via CILogon *
</GButton>
</BFormGroup>
</div>

<p class="mt-3">
<small class="text-muted">
* Galaxy uses CILogon via Custos to enable you to log in from this organization. By clicking
'Sign In', you agree to the
* Galaxy uses CILogon to enable you to log in from this organization. By clicking 'Sign In', you
agree to the
<a href="https://ca.cilogon.org/policy/privacy">CILogon</a> privacy policy and you agree to
share your username, email address, and affiliation with CILogon, Custos, and Galaxy.
share your username, email address, and affiliation with CILogon and Galaxy.
</small>
</p>
</BForm>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/User/ExternalIdentities/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const getUrl = (path) => getRootFromIndexLink() + path;
export async function disconnectIdentity(doomed) {
if (doomed) {
let url;
if (doomed.provider === "custos" || doomed.provider === "cilogon") {
if (doomed.provider === "cilogon") {
url = getUrl(`authnz/${doomed.provider}/disconnect/${doomed.email}`);
} else {
url = getUrl(`authnz/${doomed.provider}/disconnect/`);
Expand Down
2 changes: 1 addition & 1 deletion client/src/entry/analysis/modules/Login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const configMock = {
allow_local_account_creation: true,
enable_oidc: true,
mailing_join_addr: "mailing_join_addr",
prefer_custos_login: true,
prefer_oidc_login: true,
registration_warning_message: "registration_warning_message",
server_mail_configured: true,
show_welcome_with_login: true,
Expand Down
4 changes: 2 additions & 2 deletions client/src/entry/analysis/modules/Register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const configMock = {
allow_local_account_creation: true,
enable_oidc: true,
mailing_join_addr: "mailing_join_addr",
prefer_custos_login: true,
prefer_oidc_login: true,
registration_warning_message: "registration_warning_message",
server_mail_configured: true,
show_welcome_with_login: true,
Expand Down Expand Up @@ -61,7 +61,7 @@ describe("Register", () => {
expect(props.sessionCsrfToken).toBe("session_csrf_token");
expect(props.enableOidc).toBe(true);
expect(props.mailingJoinAddr).toBe("mailing_join_addr");
expect(props.preferCustosLogin).toBe(true);
expect(props.preferOidcLogin).toBe(true);
expect(props.serverMailConfigured).toBe(true);
expect(props.registrationWarningMessage).toBe("registration_warning_message");
expect(props.termsUrl).toBe("terms_url");
Expand Down
2 changes: 1 addition & 1 deletion client/src/entry/analysis/modules/Register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const sessionCsrfToken = computed(() => {
:enable-oidc="config.enable_oidc"
:mailing-join-addr="config.mailing_join_addr"
:oidc-idps="config.oidc"
:prefer-custos-login="config.prefer_custos_login"
:prefer-oidc-login="config.prefer_oidc_login"
:registration-warning-message="config.registration_warning_message"
:server-mail-configured="config.server_mail_configured"
:session-csrf-token="sessionCsrfToken"
Expand Down
4 changes: 2 additions & 2 deletions doc/source/admin/galaxy_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3817,11 +3817,11 @@


~~~~~~~~~~~~~~~~~~~~~~~
``prefer_custos_login``
``prefer_oidc_login``
~~~~~~~~~~~~~~~~~~~~~~~

:Description:
Controls the order of the login page to prefer Custos-based login
Controls the order of the login page to prefer OIDC-based login
and registration.
:Default: ``false``
:Type: bool
Expand Down
16 changes: 1 addition & 15 deletions doc/source/admin/special_topics/vault.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ There are currently 3 supported backends.
| Backend | Description |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| hashicorp | Hashicorp Vault is a secrets and encryption management system. https://www.vaultproject.io/ |
| custos | Custos is an NSF-funded project, backed by open source software that provides science gateways such as Galaxy with single sign-on, group management, and management of secrets such as access keys and OAuth2 access tokens. Custos secrets management is backed by Hashicorp's vault, but provides a convenient, always-on ReST API service. |
| database | The database backend stores secrets in an encrypted table in the Galaxy database itself. It is a convenient way to get started with a vault, and while it supports basic key rotation, we recommend using one of the other options in production. |

## Configuring Galaxy
Expand All @@ -36,7 +35,7 @@ path_prefix: /galaxy # optional
...
```

The `type` must be a valid backend type: `hashicorp`, `custos`, or `database`. At present, only a single vault backend
The `type` must be a valid backend type: `hashicorp`, or `database`. At present, only a single vault backend
is supported. The `path_prefix` property indicates the root path under which to store all vault keys. If multiple
Galaxy instances are using the same vault, a prefix can be used to uniquely identify the Galaxy instance.
If no path_prefix is provided, the prefix defaults to `/galaxy`.
Expand All @@ -50,19 +49,6 @@ vault_address: http://localhost:8200
vault_token: vault_application_token
```

## Vault configuration for Custos

```yaml
type: custos
custos_host: service.staging.usecustos.org
custos_port: 30170
custos_client_id: custos-jeREDACTEDye-10000001
custos_client_sec: OGREDACTEDBSUDHn
```

Obtaining the Custos client id and client secret requires first registering your Galaxy instance with Custos.
Visit [usecustos.org](http://usecustos.org/) for more information.

## Vault configuration for database

```yaml
Expand Down
8 changes: 0 additions & 8 deletions doc/source/lib/galaxy.authnz.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ galaxy.authnz package
Submodules
----------

galaxy.authnz.custos\_authnz module
-----------------------------------

.. automodule:: galaxy.authnz.custos_authnz
:members:
:undoc-members:
:show-inheritance:

galaxy.authnz.managers module
-----------------------------

Expand Down
61 changes: 61 additions & 0 deletions lib/galaxy/authnz/cilogon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
CILogon OpenID Connect backend for Galaxy.

This backend extends Galaxy's base OIDC implementation with CILogon-specific features.
"""

from galaxy.authnz.oidc import GalaxyOpenIdConnect


class CILogonOpenIdConnect(GalaxyOpenIdConnect):
"""
CILogon OIDC backend for Galaxy.

Inherits PKCE support, localhost development mode, and refresh token support
from GalaxyOpenIdConnect. Adds CILogon-specific configuration:
- CILogon-specific scopes including org.cilogon.userinfo
- Custom URL handling to strip /authorize suffix
- CILogon-specific IDP hint parameter (idphint)
"""

name = "cilogon"

# CILogon-specific scopes
DEFAULT_SCOPE = ["openid", "email", "profile", "org.cilogon.userinfo"]

def auth_params(self, state=None):
"""
Add CILogon-specific parameters to the authorization request.

Adds idphint parameter for CILogon IDP selection.
"""
params = super().auth_params(state)

# Add CILogon IDP hint (default: "cilogon")
idphint = self.setting("IDPHINT", "cilogon")
if idphint:
params["idphint"] = idphint

return params

def oidc_endpoint(self):
"""
Return the OIDC endpoint for configuration discovery.

CILogon URLs may include /authorize in examples, which needs to be
stripped to find the correct base URL.

Example CILogon URL:
https://cilogon.org/authorize -> https://cilogon.org
"""
# Check if custom URL is configured
base_url = self.setting("URL")
if base_url:
# Backwards compatibility: CILogon URL is sometimes given with /authorize
# Remove it to get the correct openid configuration endpoint
if base_url.endswith("/authorize"):
base_url = "/".join(base_url.split("/")[:-1])
# Remove potential trailing slash
return base_url.rstrip("/")
# Fall back to default OIDC endpoint discovery
return super().oidc_endpoint()
Loading
Loading