Merge branch 'develop' of github.com:makeplane/plane-ee into preview

This commit is contained in:
sriram veeraghanta
2024-07-02 16:41:55 +05:30
10 changed files with 125 additions and 45 deletions

View File

@@ -12,6 +12,7 @@ import {
TControllerInputFormField,
CopyField,
TCopyField,
CodeBlock,
} from "@/components/common";
// helpers
import { cn } from "@/helpers/common.helper";
@@ -73,7 +74,12 @@ export const InstanceOIDCConfigForm: FC<Props> = (props) => {
key: "OIDC_AUTHORIZE_URL",
type: "text",
label: "Authorize URL",
description: "The URL that brings up your IdP's authentication screen when your users click `Sign in with <name of IdP>`",
description: (
<>
The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "}
<CodeBlock>{"Continue with"}</CodeBlock>
</>
),
placeholder: "https://example.com/",
error: Boolean(errors.OIDC_AUTHORIZE_URL),
required: true,
@@ -109,7 +115,11 @@ export const InstanceOIDCConfigForm: FC<Props> = (props) => {
key: "OIDC_PROVIDER_NAME",
type: "text",
label: "IdP's name",
description: "Optional field for the name that your users see on the `Sign in with` button",
description: (
<>
Optional field for the name that your users see on the <CodeBlock>Continue with</CodeBlock> button
</>
),
placeholder: "Okta",
error: Boolean(errors.OIDC_PROVIDER_NAME),
required: false,
@@ -121,20 +131,32 @@ export const InstanceOIDCConfigForm: FC<Props> = (props) => {
key: "Origin_URI",
label: "Origin URI",
url: `${originURL}/auth/oidc/`,
description: "We will generate this for this Plane app. Add this as a trusted origin on your IdP's corresponding field.",
description:
"We will generate this for this Plane app. Add this as a trusted origin on your IdP's corresponding field.",
},
{
key: "Callback_URI",
label: "Callback URI",
url: `${originURL}/auth/oidc/callback/`,
description:
"We will generate this for you. Add this in the `Sign-in redirect URI` field of your IdP.",
description: (
<>
We will generate this for you.Add this in the{" "}
<CodeBlock darkerShade>Sign-in redirect URI</CodeBlock> field of
your IdP.
</>
),
},
{
key: "Logout_URI",
label: "Logout URI",
url: `${originURL}/auth/oidc/logout/`,
description: "We will generate this for you. Add this in the `Logout redirect URI` field of your IdP.",
description: (
<>
We will generate this for you. Add this in the{" "}
<CodeBlock darkerShade>Logout redirect URI</CodeBlock> field of
your IdP.
</>
),
},
];
@@ -177,8 +199,8 @@ export const InstanceOIDCConfigForm: FC<Props> = (props) => {
/>
<div className="flex flex-col gap-8">
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
<div className="pt-2 text-xl font-medium">IdP-provided details for Plane</div>
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
<div className="pt-2.5 text-xl font-medium">IdP-provided details for Plane</div>
{OIDC_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
@@ -208,7 +230,7 @@ export const InstanceOIDCConfigForm: FC<Props> = (props) => {
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for your IdP</div>
{OIDC_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />

View File

@@ -13,7 +13,7 @@ import { PageHeader } from "@/components/common";
import { AuthenticationMethodCard } from "@/components/authentication";
import { InstanceOIDCConfigForm } from "./form";
// icons
import OIDCLogo from "/public/logos/oidc-logo.png";
import OIDCLogo from "/public/logos/oidc-logo.svg";
const InstanceOIDCAuthenticationPage = observer(() => {
// store
@@ -80,7 +80,7 @@ const InstanceOIDCAuthenticationPage = observer(() => {
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md p-4">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceOIDCConfigForm config={formattedConfig} />
) : (

View File

@@ -12,11 +12,13 @@ import {
TControllerInputFormField,
CopyField,
TCopyField,
CodeBlock,
} from "@/components/common";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
import { SAMLAttributeMappingTable } from "@/plane-admin/components/authentication";
type Props = {
config: IFormattedInstanceConfiguration;
@@ -62,7 +64,12 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
key: "SAML_SSO_URL",
type: "text",
label: "SSO URL",
description: "The URL that brings up your IdP's authentication screen when your users click `Sign in with <name of IdP>`",
description: (
<>
The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "}
<CodeBlock>{"Continue with"}</CodeBlock> button
</>
),
placeholder: "https://example.com/sso",
error: Boolean(errors.SAML_SSO_URL),
required: true,
@@ -80,7 +87,11 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
key: "SAML_PROVIDER_NAME",
type: "text",
label: "IdP's name",
description: "Optional field for the name that your users see on the `Sign in with` button",
description: (
<>
Optional field for the name that your users see on the <CodeBlock>Continue with</CodeBlock> button
</>
),
placeholder: "Okta",
error: Boolean(errors.SAML_PROVIDER_NAME),
required: false,
@@ -99,14 +110,30 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
key: "Callback_URI",
label: "Callback URI",
url: `${originURL}/auth/saml/callback/`,
description:
"We will generate this `http-post request` URL that you should paste into your `ACS URL` or `Sign-in call back URL` field on your IdP.",
description: (
<>
We will generate this{" "}
<CodeBlock darkerShade>http-post request</CodeBlock> URL that you
should paste into your <CodeBlock darkerShade>ACS URL</CodeBlock>{" "}
or <CodeBlock darkerShade>Sign-in call back URL</CodeBlock> field
on your IdP.
</>
),
},
{
key: "Logout_URI",
label: "Logout URI",
url: `${originURL}/auth/saml/logout/`,
description: "We will generate this `http-redirect request` URL that you should paste into your `SLS URL` or `Logout URL` field on your IdP.",
description: (
<>
We will generate this{" "}
<CodeBlock darkerShade>http-redirect request</CodeBlock> URL that
you should paste into your{" "}
<CodeBlock darkerShade>SLS URL</CodeBlock> or{" "}
<CodeBlock darkerShade>Logout URL</CodeBlock>
field on your IdP.
</>
),
},
];
@@ -147,8 +174,8 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
/>
<div className="flex flex-col gap-8">
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
<div className="pt-2 text-xl font-medium">IdP-provided details for Plane</div>
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
<div className="pt-2.5 text-xl font-medium">IdP-provided details for Plane</div>
{SAML_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
@@ -180,7 +207,7 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
/>
)}
/>
<p className="text-xs text-custom-text-400">
<p className="pt-0.5 text-xs text-custom-text-300">
IdP-generated certificate for signing this Plane app as an authorized service provider for your IdP
</p>
</div>
@@ -200,22 +227,14 @@ export const InstanceSAMLConfigForm: FC<Props> = (props) => {
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Service provider details</div>
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for your IdP</div>
{SAML_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-200 font-medium">Name ID format</h4>
<p className="text-sm text-custom-text-100">emailAddress</p>
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-200 font-medium">Attribute mapping</h4>
<ul className="text-sm text-custom-text-100 list-disc pl-6">
<li>first_name to user.firstName</li>
<li>last_name to user.lastName</li>
<li>email to user.email</li>
</ul>
<h4 className="text-sm text-custom-text-200 font-medium">Mapping</h4>
<SAMLAttributeMappingTable />
</div>
</div>
</div>

View File

@@ -64,7 +64,7 @@ const InstanceSAMLAuthenticationPage = observer(() => {
name="SAML"
description="Authenticate your users via Security Assertion Markup Language
protocol."
icon={<Image src={SAMLLogo} height={26} width={26} alt="SAML Logo" className="pb-1 pl-0.5" />}
icon={<Image src={SAMLLogo} height={24} width={24} alt="SAML Logo" className="pl-0.5" />}
config={
<ToggleSwitch
value={Boolean(parseInt(enableSAMLConfig))}
@@ -81,7 +81,7 @@ const InstanceSAMLAuthenticationPage = observer(() => {
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md p-4">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceSAMLConfigForm config={formattedConfig} />
) : (

View File

@@ -1,6 +1,3 @@
// base components
import { getAuthenticationModes as getBaseAuthenticationModes } from "ce/components/authentication";
import { observer } from "mobx-react";
import Image from "next/image";
import { useTheme } from "next-themes";
@@ -12,9 +9,12 @@ import {
} from "@plane/types";
// components
import { AuthenticationMethodCard } from "@/components/authentication";
// helpers
import { getBaseAuthenticationModes } from "@/helpers/authentication.helper";
// plane admin components
import { OIDCConfiguration, SAMLConfiguration } from "@/plane-admin/components/authentication";
// images
import OIDCLogo from "@/public/logos/oidc-logo.png";
import OIDCLogo from "@/public/logos/oidc-logo.svg";
import SAMLLogo from "@/public/logos/saml-logo.svg";
type TInstanceAuthenticationMethodKeys = TBaseAuthenticationMethods | TInstanceEnterpriseAuthenticationMethodKeys;
@@ -41,14 +41,14 @@ export const getAuthenticationModes: (props: TGetAuthenticationModeProps) => TIn
key: "oidc",
name: "OIDC",
description: "Authenticate your users via the OpenID Connect protocol.",
icon: <Image src={OIDCLogo} height={20} width={20} alt="OIDC Logo" />,
icon: <Image src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
config: <OIDCConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "saml",
name: "SAML",
description: "Authenticate your users via the Security Assertion Markup Language protocol.",
icon: <Image src={SAMLLogo} height={24} width={24} alt="SAML Logo" className="pb-0.5 pl-0.5" />,
icon: <Image src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
config: <SAMLConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
];
@@ -68,6 +68,7 @@ export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer(
icon={method.icon}
config={method.config}
disabled={disabled}
unavailable={method.unavailable}
/>
))}
</>

View File

@@ -1,3 +1,4 @@
export * from "./authentication-modes";
export * from "./oidc-config";
export * from "./saml-config";
export * from "./saml-attribute-mapping-table";

View File

@@ -0,0 +1,28 @@
export const SAMLAttributeMappingTable = () => (
<table className="table-auto border-collapse text-custom-text-200 text-sm">
<thead>
<tr className="text-left">
<th className="border-b border-r border-custom-border-300 px-4 py-1.5">IdP</th>
<th className="border-b border-custom-border-300 px-4 py-1.5">Plane</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">Name ID format</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">emailAddress</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">first_name</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.firstName</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">last_name</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.lastName</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">email</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.email</td>
</tr>
</tbody>
</table>
);

View File

@@ -1,5 +1,6 @@
# Python imports
import requests
import os
# Django imports
from django.utils import timezone
@@ -57,7 +58,10 @@ class OauthAdapter(Adapter):
try:
headers = headers or {}
response = requests.post(
self.get_token_url(), data=data, headers=headers
self.get_token_url(),
data=data,
headers=headers,
verify=os.environ.get("SSL_VERIFY", "1") == "1",
)
response.raise_for_status()
return response.json()
@@ -73,7 +77,11 @@ class OauthAdapter(Adapter):
headers = {
"Authorization": f"Bearer {self.token_data.get('access_token')}"
}
response = requests.get(self.get_user_info_url(), headers=headers)
response = requests.get(
self.get_user_info_url(),
headers=headers,
verify=os.environ.get("SSL_VERIFY", "1") == "1",
)
response.raise_for_status()
return response.json()
except requests.RequestException:

View File

@@ -151,9 +151,7 @@ class SAMLAdapter(Adapter):
if errors:
if not self.auth.is_authenticated():
# Log the errors
log_exception(
AuthenticationException("Response not authenticated")
)
log_exception(Exception(errors))
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"SAML_PROVIDER_ERROR"
@@ -161,7 +159,7 @@ class SAMLAdapter(Adapter):
error_message="SAML_PROVIDER_ERROR",
)
# Log the errors
log_exception(AuthenticationException(errors))
log_exception(Exception(errors))
raise AuthenticationException(
error_message=AUTHENTICATION_ERROR_CODES[
"SAML_PROVIDER_ERROR"

View File

@@ -64,3 +64,6 @@ FILE_SIZE_LIMIT=5242880
# Gunicorn Workers
GUNICORN_WORKERS=2
# Add SSL verification
SSL_VERIFY=1