Show More
@@ -0,0 +1,159 b'' | |||||
|
1 | .. _config-saml-azure-ref: | |||
|
2 | ||||
|
3 | ||||
|
4 | SAML 2.0 with Azure Entra ID | |||
|
5 | ---------------------------- | |||
|
6 | ||||
|
7 | **This plugin is available only in EE Edition.** | |||
|
8 | ||||
|
9 | |RCE| supports SAML 2.0 Authentication with Azure Entra ID provider. This allows | |||
|
10 | users to log-in to RhodeCode via SSO mechanism of external identity provider | |||
|
11 | such as Azure AD. The login can be triggered either by the external IDP, or internally | |||
|
12 | by clicking specific authentication button on the log-in page. | |||
|
13 | ||||
|
14 | ||||
|
15 | Configuration steps | |||
|
16 | ^^^^^^^^^^^^^^^^^^^ | |||
|
17 | ||||
|
18 | To configure Duo Security SAML authentication, use the following steps: | |||
|
19 | ||||
|
20 | 1. From the |RCE| interface, select | |||
|
21 | :menuselection:`Admin --> Authentication` | |||
|
22 | 2. Activate the `Azure Entra ID` plugin and select :guilabel:`Save` | |||
|
23 | 3. Go to newly available menu option called `Azure Entra ID` on the left side. | |||
|
24 | 4. Check the `enabled` check box in the plugin configuration section, | |||
|
25 | and fill in the required SAML information and :guilabel:`Save`, for more details, | |||
|
26 | see :ref:`config-saml-azure` | |||
|
27 | ||||
|
28 | ||||
|
29 | .. _config-saml-azure: | |||
|
30 | ||||
|
31 | ||||
|
32 | Example SAML Azure Entra ID configuration | |||
|
33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |||
|
34 | ||||
|
35 | Example configuration for SAML 2.0 with Azure Entra ID provider | |||
|
36 | ||||
|
37 | ||||
|
38 | Enabled | |||
|
39 | `True`: | |||
|
40 | ||||
|
41 | .. note:: | |||
|
42 | Enable or disable this authentication plugin. | |||
|
43 | ||||
|
44 | ||||
|
45 | Auth Cache TTL | |||
|
46 | `30`: | |||
|
47 | ||||
|
48 | .. note:: | |||
|
49 | Amount of seconds to cache the authentication and permissions check response call for this plugin. | |||
|
50 | Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). | |||
|
51 | ||||
|
52 | Debug | |||
|
53 | `True`: | |||
|
54 | ||||
|
55 | .. note:: | |||
|
56 | Enable or disable debug mode that shows SAML errors in the RhodeCode logs. | |||
|
57 | ||||
|
58 | ||||
|
59 | Auth button name | |||
|
60 | `Azure Entra ID`: | |||
|
61 | ||||
|
62 | .. note:: | |||
|
63 | Alternative authentication display name. E.g AzureAuth, CorporateID etc. | |||
|
64 | ||||
|
65 | ||||
|
66 | Entity ID | |||
|
67 | `https://sts.windows.net/APP_ID/`: | |||
|
68 | ||||
|
69 | .. note:: | |||
|
70 | Identity Provider entity/metadata URI. Known as "Microsoft Entra Identifier" | |||
|
71 | E.g. https://sts.windows.net/abcd-c655-dcee-aab7-abcd/ | |||
|
72 | ||||
|
73 | SSO URL | |||
|
74 | `https://login.microsoftonline.com/APP_ID/saml2`: | |||
|
75 | ||||
|
76 | .. note:: | |||
|
77 | SSO (SingleSignOn) endpoint URL of the IdP. This can be used to initialize login, Known also as Login URL | |||
|
78 | E.g. https://login.microsoftonline.com/abcd-c655-dcee-aab7-abcd/saml2 | |||
|
79 | ||||
|
80 | SLO URL | |||
|
81 | `https://login.microsoftonline.com/APP_ID/saml2`: | |||
|
82 | ||||
|
83 | .. note:: | |||
|
84 | SLO (SingleLogout) endpoint URL of the IdP. , Known also as Logout URL | |||
|
85 | E.g. https://login.microsoftonline.com/abcd-c655-dcee-aab7-abcd/saml2 | |||
|
86 | ||||
|
87 | x509cert | |||
|
88 | `<CERTIFICATE_STRING>`: | |||
|
89 | ||||
|
90 | .. note:: | |||
|
91 | Identity provider public x509 certificate. It will be converted to single-line format without headers. | |||
|
92 | Download the raw base64 encoded certificate from the Identity provider and paste it here. | |||
|
93 | ||||
|
94 | SAML Signature | |||
|
95 | `sha-256`: | |||
|
96 | ||||
|
97 | .. note:: | |||
|
98 | Type of Algorithm to use for verification of SAML signature on Identity provider side. | |||
|
99 | ||||
|
100 | SAML Digest | |||
|
101 | `sha-256`: | |||
|
102 | ||||
|
103 | .. note:: | |||
|
104 | Type of Algorithm to use for verification of SAML digest on Identity provider side. | |||
|
105 | ||||
|
106 | Service Provider Cert Dir | |||
|
107 | `/etc/rhodecode/conf/saml_ssl/`: | |||
|
108 | ||||
|
109 | .. note:: | |||
|
110 | Optional directory to store service provider certificate and private keys. | |||
|
111 | Expected certs for the SP should be stored in this folder as: | |||
|
112 | * sp.key Private Key | |||
|
113 | * sp.crt Public cert | |||
|
114 | * sp_new.crt Future Public cert | |||
|
115 | ||||
|
116 | Also you can use other cert to sign the metadata of the SP using the: | |||
|
117 | * metadata.key | |||
|
118 | * metadata.crt | |||
|
119 | ||||
|
120 | Expected NameID Format | |||
|
121 | `nameid-format:emailAddress`: | |||
|
122 | ||||
|
123 | .. note:: | |||
|
124 | The format that specifies how the NameID is sent to the service provider. | |||
|
125 | ||||
|
126 | User ID Attribute | |||
|
127 | `user.email`: | |||
|
128 | ||||
|
129 | .. note:: | |||
|
130 | User ID Attribute name. This defines which attribute in SAML response will be used to link accounts via unique id. | |||
|
131 | Ensure this is returned from DuoSecurity for example via duo_username. | |||
|
132 | ||||
|
133 | Username Attribute | |||
|
134 | `user.username`: | |||
|
135 | ||||
|
136 | .. note:: | |||
|
137 | Username Attribute name. This defines which attribute in SAML response will map to a username. | |||
|
138 | ||||
|
139 | Email Attribute | |||
|
140 | `user.email`: | |||
|
141 | ||||
|
142 | .. note:: | |||
|
143 | Email Attribute name. This defines which attribute in SAML response will map to an email address. | |||
|
144 | ||||
|
145 | ||||
|
146 | ||||
|
147 | Below is example setup from Azure Administration page that can be used with above config. | |||
|
148 | ||||
|
149 | .. image:: ../images/saml-azure-service-provider-example.png | |||
|
150 | :alt: Azure SAML setup example | |||
|
151 | :scale: 50 % | |||
|
152 | ||||
|
153 | ||||
|
154 | Below is an example attribute mapping set for IDP provider required by the above config. | |||
|
155 | ||||
|
156 | ||||
|
157 | .. image:: ../images/saml-azure-attributes-example.png | |||
|
158 | :alt: Azure SAML setup example | |||
|
159 | :scale: 50 % No newline at end of file |
@@ -1,24 +1,39 b'' | |||||
1 | FROM python:3.12.0-bullseye |
|
1 | FROM python:3.12.0-bullseye | |
2 |
|
2 | |||
3 | WORKDIR /project |
|
3 | WORKDIR /project | |
4 |
|
4 | |||
5 | RUN apt-get update \ |
|
5 | RUN apt-get update \ | |
6 | && apt-get install --no-install-recommends --yes \ |
|
6 | && apt-get install --no-install-recommends --yes \ | |
7 |
|
|
7 | curl \ | |
8 |
|
|
8 | zip \ | |
9 |
|
|
9 | graphviz \ | |
10 | imagemagick \ |
|
10 | dvipng \ | |
11 |
|
|
11 | imagemagick \ | |
|
12 | make \ | |||
|
13 | latexmk \ | |||
|
14 | texlive-latex-recommended \ | |||
|
15 | texlive-latex-extra \ | |||
|
16 | texlive-xetex \ | |||
|
17 | fonts-freefont-otf \ | |||
|
18 | texlive-fonts-recommended \ | |||
|
19 | texlive-lang-greek \ | |||
|
20 | tex-gyre \ | |||
12 | && apt-get autoremove \ |
|
21 | && apt-get autoremove \ | |
13 | && apt-get clean \ |
|
22 | && apt-get clean \ | |
14 | && rm -rf /var/lib/apt/lists/* |
|
23 | && rm -rf /var/lib/apt/lists/* | |
15 |
|
24 | |||
|
25 | RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ | |||
|
26 | unzip awscliv2.zip && \ | |||
|
27 | ./aws/install && \ | |||
|
28 | rm -rf ./aws && \ | |||
|
29 | rm awscliv2.zip | |||
|
30 | ||||
16 | RUN \ |
|
31 | RUN \ | |
17 | python3 -m pip install --no-cache-dir --upgrade pip && \ |
|
32 | python3 -m pip install --no-cache-dir --upgrade pip && \ | |
18 | python3 -m pip install --no-cache-dir Sphinx Pillow |
|
33 | python3 -m pip install --no-cache-dir Sphinx Pillow | |
19 |
|
34 | |||
20 | ADD requirements_docs.txt /project |
|
35 | ADD requirements_docs.txt /project | |
21 | RUN \ |
|
36 | RUN \ | |
22 | python3 -m pip install -r requirements_docs.txt |
|
37 | python3 -m pip install -r requirements_docs.txt | |
23 |
|
38 | |||
24 | CMD ["sphinx-build", "-M", "html", ".", "_build"] |
|
39 | CMD ["sphinx-build", "-M", "html", ".", "_build"] |
@@ -1,88 +1,90 b'' | |||||
1 | .. _auth-saml-bulk-enroll-users-ref: |
|
1 | .. _auth-saml-bulk-enroll-users-ref: | |
2 |
|
2 | |||
3 |
|
3 | |||
4 | Bulk enroll multiple existing users |
|
4 | Bulk enroll multiple existing users | |
5 | ----------------------------------- |
|
5 | ----------------------------------- | |
6 |
|
6 | |||
7 |
|
7 | |||
8 | RhodeCode Supports standard SAML 2.0 SSO for the web-application part. |
|
8 | RhodeCode Supports standard SAML 2.0 SSO for the web-application part. | |
9 | Below is an example how to enroll list of all or some users to use SAML authentication. |
|
9 | Below is an example how to enroll list of all or some users to use SAML authentication. | |
10 | This method simply enables SAML authentication for many users at once. |
|
10 | This method simply enables SAML authentication for many users at once. | |
11 |
|
11 | |||
12 |
|
12 | |||
13 | From the server RhodeCode Enterprise is running run ishell on the instance which we |
|
13 | From the server RhodeCode Enterprise is running run ishell on the instance which we | |
14 | want to apply the SAML migration:: |
|
14 | want to apply the SAML migration:: | |
15 |
|
15 | |||
16 | rccontrol ishell enterprise-1 |
|
16 | ./rcstack cli ishell | |
17 |
|
17 | |||
18 | Follow these steps to enable SAML authentication for multiple users. |
|
18 | Follow these steps to enable SAML authentication for multiple users. | |
19 |
|
19 | |||
20 |
|
20 | |||
21 | 1) Create a user_id => attribute mapping |
|
21 | 1) Create a user_id => attribute mapping | |
22 |
|
22 | |||
23 |
|
23 | |||
24 | `saml2user` is a mapping of external ID from SAML provider such as OneLogin, DuoSecurity, Google. |
|
24 | `saml2user` is a mapping of external ID from SAML provider such as OneLogin, DuoSecurity, Google. | |
25 | This mapping consists of local rhodecode user_id mapped to set of required attributes needed to bind SAML |
|
25 | This mapping consists of local rhodecode user_id mapped to set of required attributes needed to bind SAML | |
26 | account to internal rhodecode user. |
|
26 | account to internal rhodecode user. | |
27 | For example, 123 is local rhodecode user_id, and '48253211' is OneLogin ID. |
|
27 | For example, 123 is local rhodecode user_id, and '48253211' is OneLogin ID. | |
28 | For other providers you'd have to figure out what would be the user-id, sometimes it's the email, i.e for Google |
|
28 | For other providers you'd have to figure out what would be the user-id, sometimes it's the email, i.e for Google | |
29 | The most important this id needs to be unique for each user. |
|
29 | The most important this id needs to be unique for each user. | |
30 |
|
30 | |||
31 | .. code-block:: python |
|
31 | .. code-block:: python | |
32 |
|
32 | |||
33 | In [1]: saml2user = { |
|
33 | In [1]: saml2user = { | |
34 | ...: # OneLogin, uses externalID available to read from in the UI |
|
34 | ...: # OneLogin, uses externalID available to read from in the UI | |
35 | ...: 123: {'id': '48253211'}, |
|
35 | ...: 123: {'id': '48253211'}, | |
36 | ...: # for Google/DuoSecurity email is also an option for unique ID |
|
36 | ...: # for Google/DuoSecurity email is also an option for unique ID | |
37 | ...: 124: {'id': 'email@domain.com'}, |
|
37 | ...: 124: {'id': 'email@domain.com'}, | |
38 | ...: } |
|
38 | ...: } | |
39 |
|
39 | |||
40 |
|
40 | |||
41 | 2) Import the plugin you want to run migration for. |
|
41 | 2) Import the plugin you want to run migration for. | |
42 |
|
42 | |||
43 | From available options pick only one and run the `import` statement |
|
43 | From available options pick only one and run the `import` statement | |
44 |
|
44 | |||
45 | .. code-block:: python |
|
45 | .. code-block:: python | |
46 |
|
46 | |||
47 | # for Duo Security |
|
47 | # for Duo Security | |
48 | In [2]: from rc_auth_plugins.auth_duo_security import RhodeCodeAuthPlugin |
|
48 | In [2]: from rc_auth_plugins.auth_duo_security import RhodeCodeAuthPlugin | |
|
49 | # for Azure Entra | |||
|
50 | In [2]: from rc_auth_plugins.auth_azure import RhodeCodeAuthPlugin | |||
49 | # for OneLogin |
|
51 | # for OneLogin | |
50 | In [2]: from rc_auth_plugins.auth_onelogin import RhodeCodeAuthPlugin |
|
52 | In [2]: from rc_auth_plugins.auth_onelogin import RhodeCodeAuthPlugin | |
51 | # generic SAML plugin |
|
53 | # generic SAML plugin | |
52 | In [2]: from rc_auth_plugins.auth_saml import RhodeCodeAuthPlugin |
|
54 | In [2]: from rc_auth_plugins.auth_saml import RhodeCodeAuthPlugin | |
53 |
|
55 | |||
54 | 3) Run the migration based on saml2user mapping. |
|
56 | 3) Run the migration based on saml2user mapping. | |
55 |
|
57 | |||
56 | Enter in the ishell prompt |
|
58 | Enter in the ishell prompt | |
57 |
|
59 | |||
58 | .. code-block:: python |
|
60 | .. code-block:: python | |
59 |
|
61 | |||
60 | In [3]: for user in User.get_all(): |
|
62 | In [3]: for user in User.get_all(): | |
61 | ...: existing_identity = ExternalIdentity().query().filter(ExternalIdentity.local_user_id == user.user_id).scalar() |
|
63 | ...: existing_identity = ExternalIdentity().query().filter(ExternalIdentity.local_user_id == user.user_id).scalar() | |
62 | ...: attrs = saml2user.get(user.user_id) |
|
64 | ...: attrs = saml2user.get(user.user_id) | |
63 | ...: provider = RhodeCodeAuthPlugin.uid |
|
65 | ...: provider = RhodeCodeAuthPlugin.uid | |
64 | ...: if existing_identity: |
|
66 | ...: if existing_identity: | |
65 |
...: print('Identity for user `{ |
|
67 | ...: print(f'Identity for user `{user.username}` already exists, skipping') | |
66 | ...: continue |
|
68 | ...: continue | |
67 | ...: if attrs: |
|
69 | ...: if attrs: | |
68 | ...: external_id = attrs['id'] |
|
70 | ...: external_id = attrs['id'] | |
69 | ...: new_external_identity = ExternalIdentity() |
|
71 | ...: new_external_identity = ExternalIdentity() | |
70 | ...: new_external_identity.external_id = external_id |
|
72 | ...: new_external_identity.external_id = external_id | |
71 |
...: new_external_identity.external_username = '{ |
|
73 | ...: new_external_identity.external_username = f'{user.username}-saml-{user.user_id}' | |
72 | ...: new_external_identity.provider_name = provider |
|
74 | ...: new_external_identity.provider_name = provider | |
73 | ...: new_external_identity.local_user_id = user.user_id |
|
75 | ...: new_external_identity.local_user_id = user.user_id | |
74 | ...: new_external_identity.access_token = '' |
|
76 | ...: new_external_identity.access_token = '' | |
75 | ...: new_external_identity.token_secret = '' |
|
77 | ...: new_external_identity.token_secret = '' | |
76 | ...: new_external_identity.alt_token = '' |
|
78 | ...: new_external_identity.alt_token = '' | |
77 | ...: Session().add(ex_identity) |
|
79 | ...: Session().add(ex_identity) | |
78 | ...: Session().commit() |
|
80 | ...: Session().commit() | |
79 |
...: print('Set user `{ |
|
81 | ...: print(f'Set user `{user.username}` external identity bound to ExternalID:{external_id}') | |
80 |
|
82 | |||
81 | .. note:: |
|
83 | .. note:: | |
82 |
|
84 | |||
83 | saml2user can be really big and hard to maintain in ishell. It's also possible |
|
85 | saml2user can be really big and hard to maintain in ishell. It's also possible | |
84 | to load it as a JSON file prepared before and stored on disk. To do so run:: |
|
86 | to load it as a JSON file prepared before and stored on disk. To do so run:: | |
85 |
|
87 | |||
86 | import json |
|
88 | import json | |
87 | saml2user = json.loads(open('/path/to/saml2user.json','rb').read()) |
|
89 | saml2user = json.loads(open('/path/to/saml2user.json','rb').read()) | |
88 |
|
90 |
@@ -1,105 +1,159 b'' | |||||
1 | .. _config-saml-duosecurity-ref: |
|
1 | .. _config-saml-duosecurity-ref: | |
2 |
|
2 | |||
3 |
|
3 | |||
4 | SAML 2.0 with Duo Security |
|
4 | SAML 2.0 with Duo Security | |
5 | -------------------------- |
|
5 | -------------------------- | |
6 |
|
6 | |||
7 | **This plugin is available only in EE Edition.** |
|
7 | **This plugin is available only in EE Edition.** | |
8 |
|
8 | |||
9 | |RCE| supports SAML 2.0 Authentication with Duo Security provider. This allows |
|
9 | |RCE| supports SAML 2.0 Authentication with Duo Security provider. This allows | |
10 | users to log-in to RhodeCode via SSO mechanism of external identity provider |
|
10 | users to log-in to RhodeCode via SSO mechanism of external identity provider | |
11 | such as Duo. The login can be triggered either by the external IDP, or internally |
|
11 | such as Duo. The login can be triggered either by the external IDP, or internally | |
12 | by clicking specific authentication button on the log-in page. |
|
12 | by clicking specific authentication button on the log-in page. | |
13 |
|
13 | |||
14 |
|
14 | |||
15 | Configuration steps |
|
15 | Configuration steps | |
16 | ^^^^^^^^^^^^^^^^^^^ |
|
16 | ^^^^^^^^^^^^^^^^^^^ | |
17 |
|
17 | |||
18 | To configure Duo Security SAML authentication, use the following steps: |
|
18 | To configure Duo Security SAML authentication, use the following steps: | |
19 |
|
19 | |||
20 | 1. From the |RCE| interface, select |
|
20 | 1. From the |RCE| interface, select | |
21 | :menuselection:`Admin --> Authentication` |
|
21 | :menuselection:`Admin --> Authentication` | |
22 | 2. Activate the `Duo Security` plugin and select :guilabel:`Save` |
|
22 | 2. Activate the `Duo Security` plugin and select :guilabel:`Save` | |
23 | 3. Go to newly available menu option called `Duo Security` on the left side. |
|
23 | 3. Go to newly available menu option called `Duo Security` on the left side. | |
24 | 4. Check the `enabled` check box in the plugin configuration section, |
|
24 | 4. Check the `enabled` check box in the plugin configuration section, | |
25 | and fill in the required SAML information and :guilabel:`Save`, for more details, |
|
25 | and fill in the required SAML information and :guilabel:`Save`, for more details, | |
26 | see :ref:`config-saml-duosecurity` |
|
26 | see :ref:`config-saml-duosecurity` | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | .. _config-saml-duosecurity: |
|
29 | .. _config-saml-duosecurity: | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | Example SAML Duo Security configuration |
|
32 | Example SAML Duo Security configuration | |
33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
34 |
|
34 | |||
35 |
Example configuration for SAML 2.0 with Duo Security provider |
|
35 | Example configuration for SAML 2.0 with Duo Security provider | |
|
36 | ||||
|
37 | ||||
|
38 | Enabled | |||
|
39 | `True`: | |||
36 |
|
40 | |||
37 | *option*: `enabled` => `True` |
|
41 | .. note:: | |
38 |
|
|
42 | Enable or disable this authentication plugin. | |
|
43 | ||||
|
44 | ||||
|
45 | Auth Cache TTL | |||
|
46 | `30`: | |||
39 |
|
47 | |||
40 | *option*: `cache_ttl` => `0` |
|
48 | .. note:: | |
41 |
|
|
49 | Amount of seconds to cache the authentication and permissions check response call for this plugin. | |
42 |
|
|
50 | Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). | |
|
51 | ||||
|
52 | Debug | |||
|
53 | `True`: | |||
43 |
|
54 | |||
44 | *option*: `debug` => `True` |
|
55 | .. note:: | |
45 |
|
|
56 | Enable or disable debug mode that shows SAML errors in the RhodeCode logs. | |
|
57 | ||||
|
58 | ||||
|
59 | Auth button name | |||
|
60 | `Azure Entra ID`: | |||
46 |
|
61 | |||
47 | *option*: `entity_id` => `http://rc-app.com/dag/saml2/idp/metadata.php` |
|
62 | .. note:: | |
48 | # Identity Provider entity/metadata URI. |
|
63 | Alternative authentication display name. E.g AzureAuth, CorporateID etc. | |
49 | # E.g. https://duo-gateway.com/dag/saml2/idp/metadata.php |
|
64 | ||
|
65 | ||||
|
66 | Entity ID | |||
|
67 | `https://my-duo-gateway.com/dag/saml2/idp/metadata.php`: | |||
|
68 | ||||
|
69 | .. note:: | |||
|
70 | Identity Provider entity/metadata URI. | |||
|
71 | E.g. https://duo-gateway.com/dag/saml2/idp/metadata.php | |||
|
72 | ||||
|
73 | SSO URL | |||
|
74 | `https://duo-gateway.com/dag/saml2/idp/SSOService.php?spentityid=<metadata_entity_id>`: | |||
50 |
|
75 | |||
51 | *option*: `sso_service_url` => `http://rc-app.com/dag/saml2/idp/SSOService.php?spentityid=http://rc.local.pl/_admin/auth/duosecurity/saml-metadata` |
|
76 | .. note:: | |
52 |
|
|
77 | SSO (SingleSignOn) endpoint URL of the IdP. This can be used to initialize login, Known also as Login URL | |
53 |
|
|
78 | E.g. http://rc-app.com/dag/saml2/idp/SSOService.php?spentityid=https://docker-dev/_admin/auth/duosecurity/saml-metadata | |
|
79 | ||||
|
80 | SLO URL | |||
|
81 | `https://duo-gateway.com/dag/saml2/idp/SingleLogoutService.php?ReturnTo=<return_url>`: | |||
54 |
|
82 | |||
55 | *option*: `slo_service_url` => `http://rc-app.com/dag/saml2/idp/SingleLogoutService.php?ReturnTo=http://rc-app.com/dag/module.php/duosecurity/logout.php` |
|
83 | .. note:: | |
56 |
|
|
84 | SLO (SingleLogout) endpoint URL of the IdP. , Known also as Logout URL | |
57 |
|
|
85 | E.g. http://rc-app.com/dag/saml2/idp/SingleLogoutService.php?ReturnTo=https://docker-dev/_admin/auth/duosecurity/saml-sign-out-endpoint | |
58 |
|
86 | |||
59 | *option*: `x509cert` => `<CERTIFICATE_STRING>` |
|
87 | x509cert | |
60 | # Identity provider public x509 certificate. It will be converted to single-line format without headers |
|
88 | `<CERTIFICATE_STRING>`: | |
61 |
|
89 | |||
62 | *option*: `name_id_format` => `sha-1` |
|
90 | .. note:: | |
63 | # The format that specifies how the NameID is sent to the service provider. |
|
91 | Identity provider public x509 certificate. It will be converted to single-line format without headers. | |
|
92 | Download the raw base64 encoded certificate from the Identity provider and paste it here. | |||
|
93 | ||||
|
94 | SAML Signature | |||
|
95 | `sha-256`: | |||
|
96 | ||||
|
97 | .. note:: | |||
|
98 | Type of Algorithm to use for verification of SAML signature on Identity provider side. | |||
|
99 | ||||
|
100 | SAML Digest | |||
|
101 | `sha-256`: | |||
64 |
|
102 | |||
65 | *option*: `signature_algo` => `sha-256` |
|
103 | .. note:: | |
66 |
|
|
104 | Type of Algorithm to use for verification of SAML digest on Identity provider side. | |
|
105 | ||||
|
106 | Service Provider Cert Dir | |||
|
107 | `/etc/rhodecode/conf/saml_ssl/`: | |||
67 |
|
108 | |||
68 | *option*: `digest_algo` => `sha-256` |
|
109 | .. note:: | |
69 | # Type of Algorithm to use for verification of SAML digest on Identity provider side |
|
110 | Optional directory to store service provider certificate and private keys. | |
|
111 | Expected certs for the SP should be stored in this folder as: | |||
|
112 | * sp.key Private Key | |||
|
113 | * sp.crt Public cert | |||
|
114 | * sp_new.crt Future Public cert | |||
70 |
|
115 | |||
71 | *option*: `cert_dir` => `/etc/saml/` |
|
116 | Also you can use other cert to sign the metadata of the SP using the: | |
72 | # Optional directory to store service provider certificate and private keys. |
|
117 | * metadata.key | |
73 | # Expected certs for the SP should be stored in this folder as: |
|
118 | * metadata.crt | |
74 | # * sp.key Private Key |
|
119 | ||
75 | # * sp.crt Public cert |
|
120 | Expected NameID Format | |
76 | # * sp_new.crt Future Public cert |
|
121 | `nameid-format:emailAddress`: | |
77 | # |
|
122 | ||
78 | # Also you can use other cert to sign the metadata of the SP using the: |
|
123 | .. note:: | |
79 | # * metadata.key |
|
124 | The format that specifies how the NameID is sent to the service provider. | |
80 | # * metadata.crt |
|
125 | ||
|
126 | User ID Attribute | |||
|
127 | `PersonImmutableID`: | |||
81 |
|
128 | |||
82 | *option*: `user_id_attribute` => `PersonImmutableID` |
|
129 | .. note:: | |
83 |
|
|
130 | User ID Attribute name. This defines which attribute in SAML response will be used to link accounts via unique id. | |
84 |
|
|
131 | Ensure this is returned from DuoSecurity for example via duo_username. | |
|
132 | ||||
|
133 | Username Attribute | |||
|
134 | `User.username`: | |||
85 |
|
135 | |||
86 | *option*: `username_attribute` => `User.username` |
|
136 | .. note:: | |
87 |
|
|
137 | Username Attribute name. This defines which attribute in SAML response will map to a username. | |
88 |
|
138 | |||
89 | *option*: `email_attribute` => `User.email` |
|
139 | Email Attribute | |
90 | # Email Attribute name. This defines which attribute in SAML response will map to an email address. |
|
140 | `User.email`: | |
|
141 | ||||
|
142 | .. note:: | |||
|
143 | Email Attribute name. This defines which attribute in SAML response will map to an email address. | |||
|
144 | ||||
91 |
|
145 | |||
92 |
|
146 | |||
93 | Below is example setup from DUO Administration page that can be used with above config. |
|
147 | Below is example setup from DUO Administration page that can be used with above config. | |
94 |
|
148 | |||
95 | .. image:: ../images/saml-duosecurity-service-provider-example.png |
|
149 | .. image:: ../images/saml-duosecurity-service-provider-example.png | |
96 | :alt: DUO Security SAML setup example |
|
150 | :alt: DUO Security SAML setup example | |
97 | :scale: 50 % |
|
151 | :scale: 50 % | |
98 |
|
152 | |||
99 |
|
153 | |||
100 | Below is an example attribute mapping set for IDP provider required by the above config. |
|
154 | Below is an example attribute mapping set for IDP provider required by the above config. | |
101 |
|
155 | |||
102 |
|
156 | |||
103 | .. image:: ../images/saml-duosecurity-attributes-example.png |
|
157 | .. image:: ../images/saml-duosecurity-attributes-example.png | |
104 | :alt: DUO Security SAML setup example |
|
158 | :alt: DUO Security SAML setup example | |
105 | :scale: 50 % No newline at end of file |
|
159 | :scale: 50 % |
@@ -1,19 +1,20 b'' | |||||
1 | .. _config-saml-generic-ref: |
|
1 | .. _config-saml-generic-ref: | |
2 |
|
2 | |||
3 |
|
3 | |||
4 | SAML 2.0 Authentication |
|
4 | SAML 2.0 Authentication | |
5 | ----------------------- |
|
5 | ----------------------- | |
6 |
|
6 | |||
7 |
|
7 | |||
8 | **This plugin is available only in EE Edition.** |
|
8 | **This plugin is available only in EE Edition.** | |
9 |
|
9 | |||
10 | RhodeCode Supports standard SAML 2.0 SSO for the web-application part. |
|
10 | RhodeCode Supports standard SAML 2.0 SSO for the web-application part. | |
11 |
|
11 | |||
12 | Please check for reference two example providers: |
|
12 | Please check for reference two example providers: | |
13 |
|
13 | |||
14 | .. toctree:: |
|
14 | .. toctree:: | |
15 |
|
15 | |||
16 | auth-saml-duosecurity |
|
16 | auth-saml-duosecurity | |
17 | auth-saml-onelogin |
|
17 | auth-saml-onelogin | |
|
18 | auth-saml-azure | |||
18 | auth-saml-bulk-enroll-users |
|
19 | auth-saml-bulk-enroll-users | |
19 |
|
20 |
@@ -1,106 +1,159 b'' | |||||
1 | .. _config-saml-onelogin-ref: |
|
1 | .. _config-saml-onelogin-ref: | |
2 |
|
2 | |||
3 |
|
3 | |||
4 | SAML 2.0 with One Login |
|
4 | SAML 2.0 with One Login | |
5 | ----------------------- |
|
5 | ----------------------- | |
6 |
|
6 | |||
7 | **This plugin is available only in EE Edition.** |
|
7 | **This plugin is available only in EE Edition.** | |
8 |
|
8 | |||
9 | |RCE| supports SAML 2.0 Authentication with OneLogin provider. This allows |
|
9 | |RCE| supports SAML 2.0 Authentication with OneLogin provider. This allows | |
10 | users to log-in to RhodeCode via SSO mechanism of external identity provider |
|
10 | users to log-in to RhodeCode via SSO mechanism of external identity provider | |
11 | such as OneLogin. The login can be triggered either by the external IDP, or internally |
|
11 | such as OneLogin. The login can be triggered either by the external IDP, or internally | |
12 | by clicking specific authentication button on the log-in page. |
|
12 | by clicking specific authentication button on the log-in page. | |
13 |
|
13 | |||
14 |
|
14 | |||
15 | Configuration steps |
|
15 | Configuration steps | |
16 | ^^^^^^^^^^^^^^^^^^^ |
|
16 | ^^^^^^^^^^^^^^^^^^^ | |
17 |
|
17 | |||
18 | To configure OneLogin SAML authentication, use the following steps: |
|
18 | To configure OneLogin SAML authentication, use the following steps: | |
19 |
|
19 | |||
20 | 1. From the |RCE| interface, select |
|
20 | 1. From the |RCE| interface, select | |
21 | :menuselection:`Admin --> Authentication` |
|
21 | :menuselection:`Admin --> Authentication` | |
22 | 2. Activate the `OneLogin` plugin and select :guilabel:`Save` |
|
22 | 2. Activate the `OneLogin` plugin and select :guilabel:`Save` | |
23 | 3. Go to newly available menu option called `OneLogin` on the left side. |
|
23 | 3. Go to newly available menu option called `OneLogin` on the left side. | |
24 | 4. Check the `enabled` check box in the plugin configuration section, |
|
24 | 4. Check the `enabled` check box in the plugin configuration section, | |
25 | and fill in the required SAML information and :guilabel:`Save`, for more details, |
|
25 | and fill in the required SAML information and :guilabel:`Save`, for more details, | |
26 | see :ref:`config-saml-onelogin` |
|
26 | see :ref:`config-saml-onelogin` | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | .. _config-saml-onelogin: |
|
29 | .. _config-saml-onelogin: | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | Example SAML OneLogin configuration |
|
32 | Example SAML OneLogin configuration | |
33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
34 |
|
34 | |||
35 |
Example configuration for SAML 2.0 with OneLogin provider |
|
35 | Example configuration for SAML 2.0 with OneLogin provider | |
|
36 | ||||
|
37 | ||||
|
38 | Enabled | |||
|
39 | `True`: | |||
36 |
|
40 | |||
37 | *option*: `enabled` => `True` |
|
41 | .. note:: | |
38 |
|
|
42 | Enable or disable this authentication plugin. | |
|
43 | ||||
|
44 | ||||
|
45 | Auth Cache TTL | |||
|
46 | `30`: | |||
39 |
|
47 | |||
40 | *option*: `cache_ttl` => `0` |
|
48 | .. note:: | |
41 |
|
|
49 | Amount of seconds to cache the authentication and permissions check response call for this plugin. | |
42 |
|
|
50 | Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). | |
|
51 | ||||
|
52 | Debug | |||
|
53 | `True`: | |||
43 |
|
54 | |||
44 | *option*: `debug` => `True` |
|
55 | .. note:: | |
45 |
|
|
56 | Enable or disable debug mode that shows SAML errors in the RhodeCode logs. | |
|
57 | ||||
|
58 | ||||
|
59 | Auth button name | |||
|
60 | `Azure Entra ID`: | |||
46 |
|
61 | |||
47 | *option*: `entity_id` => `https://app.onelogin.com/saml/metadata/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
|
62 | .. note:: | |
48 | # Identity Provider entity/metadata URI. |
|
63 | Alternative authentication display name. E.g AzureAuth, CorporateID etc. | |
49 | # E.g. https://app.onelogin.com/saml/metadata/<onelogin_connector_id> |
|
64 | ||
|
65 | ||||
|
66 | Entity ID | |||
|
67 | `https://app.onelogin.com/saml/metadata/<onelogin_connector_id>`: | |||
|
68 | ||||
|
69 | .. note:: | |||
|
70 | Identity Provider entity/metadata URI. | |||
|
71 | E.g. https://app.onelogin.com/saml/metadata/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | |||
|
72 | ||||
|
73 | SSO URL | |||
|
74 | `https://app.onelogin.com/trust/saml2/http-post/sso/<onelogin_connector_id>`: | |||
50 |
|
75 | |||
51 | *option*: `sso_service_url` => `https://customer-domain.onelogin.com/trust/saml2/http-post/sso/xxxxxx` |
|
76 | .. note:: | |
52 |
|
|
77 | SSO (SingleSignOn) endpoint URL of the IdP. This can be used to initialize login, Known also as Login URL | |
53 |
|
|
78 | E.g. https://app.onelogin.com/trust/saml2/http-post/sso/<onelogin_connector_id> | |
|
79 | ||||
|
80 | SLO URL | |||
|
81 | `https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id>`: | |||
54 |
|
82 | |||
55 | *option*: `slo_service_url` => `https://customer-domain.onelogin.com/trust/saml2/http-redirect/slo/xxxxxx` |
|
83 | .. note:: | |
56 |
|
|
84 | SLO (SingleLogout) endpoint URL of the IdP. , Known also as Logout URL | |
57 |
|
|
85 | E.g. https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id> | |
58 |
|
86 | |||
59 | *option*: `x509cert` => `<CERTIFICATE_STRING>` |
|
87 | x509cert | |
60 | # Identity provider public x509 certificate. It will be converted to single-line format without headers |
|
88 | `<CERTIFICATE_STRING>`: | |
61 |
|
89 | |||
62 | *option*: `name_id_format` => `sha-1` |
|
90 | .. note:: | |
63 | # The format that specifies how the NameID is sent to the service provider. |
|
91 | Identity provider public x509 certificate. It will be converted to single-line format without headers. | |
|
92 | Download the raw base64 encoded certificate from the Identity provider and paste it here. | |||
|
93 | ||||
|
94 | SAML Signature | |||
|
95 | `sha-256`: | |||
|
96 | ||||
|
97 | .. note:: | |||
|
98 | Type of Algorithm to use for verification of SAML signature on Identity provider side. | |||
|
99 | ||||
|
100 | SAML Digest | |||
|
101 | `sha-256`: | |||
64 |
|
102 | |||
65 | *option*: `signature_algo` => `sha-256` |
|
103 | .. note:: | |
66 |
|
|
104 | Type of Algorithm to use for verification of SAML digest on Identity provider side. | |
|
105 | ||||
|
106 | Service Provider Cert Dir | |||
|
107 | `/etc/rhodecode/conf/saml_ssl/`: | |||
67 |
|
108 | |||
68 | *option*: `digest_algo` => `sha-256` |
|
109 | .. note:: | |
69 | # Type of Algorithm to use for verification of SAML digest on Identity provider side |
|
110 | Optional directory to store service provider certificate and private keys. | |
|
111 | Expected certs for the SP should be stored in this folder as: | |||
|
112 | * sp.key Private Key | |||
|
113 | * sp.crt Public cert | |||
|
114 | * sp_new.crt Future Public cert | |||
70 |
|
115 | |||
71 | *option*: `cert_dir` => `/etc/saml/` |
|
116 | Also you can use other cert to sign the metadata of the SP using the: | |
72 | # Optional directory to store service provider certificate and private keys. |
|
117 | * metadata.key | |
73 | # Expected certs for the SP should be stored in this folder as: |
|
118 | * metadata.crt | |
74 | # * sp.key Private Key |
|
119 | ||
75 | # * sp.crt Public cert |
|
120 | Expected NameID Format | |
76 | # * sp_new.crt Future Public cert |
|
121 | `nameid-format:emailAddress`: | |
77 | # |
|
122 | ||
78 | # Also you can use other cert to sign the metadata of the SP using the: |
|
123 | .. note:: | |
79 | # * metadata.key |
|
124 | The format that specifies how the NameID is sent to the service provider. | |
80 | # * metadata.crt |
|
125 | ||
|
126 | User ID Attribute | |||
|
127 | `PersonImmutableID`: | |||
81 |
|
128 | |||
82 | *option*: `user_id_attribute` => `PersonImmutableID` |
|
129 | .. note:: | |
83 |
|
|
130 | User ID Attribute name. This defines which attribute in SAML response will be used to link accounts via unique id. | |
84 |
|
|
131 | Ensure this is returned from DuoSecurity for example via duo_username. | |
|
132 | ||||
|
133 | Username Attribute | |||
|
134 | `User.username`: | |||
85 |
|
135 | |||
86 | *option*: `username_attribute` => `User.username` |
|
136 | .. note:: | |
87 |
|
|
137 | Username Attribute name. This defines which attribute in SAML response will map to a username. | |
88 |
|
138 | |||
89 | *option*: `email_attribute` => `User.email` |
|
139 | Email Attribute | |
90 | # Email Attribute name. This defines which attribute in SAML response will map to an email address. |
|
140 | `User.email`: | |
|
141 | ||||
|
142 | .. note:: | |||
|
143 | Email Attribute name. This defines which attribute in SAML response will map to an email address. | |||
91 |
|
144 | |||
92 |
|
145 | |||
93 |
|
146 | |||
94 | Below is example setup that can be used with OneLogin SAML authentication that can be used with above config.. |
|
147 | Below is example setup that can be used with OneLogin SAML authentication that can be used with above config.. | |
95 |
|
148 | |||
96 | .. image:: ../images/saml-onelogin-config-example.png |
|
149 | .. image:: ../images/saml-onelogin-config-example.png | |
97 | :alt: OneLogin SAML setup example |
|
150 | :alt: OneLogin SAML setup example | |
98 | :scale: 50 % |
|
151 | :scale: 50 % | |
99 |
|
152 | |||
100 |
|
153 | |||
101 | Below is an example attribute mapping set for IDP provider required by the above config. |
|
154 | Below is an example attribute mapping set for IDP provider required by the above config. | |
102 |
|
155 | |||
103 |
|
156 | |||
104 | .. image:: ../images/saml-onelogin-attributes-example.png |
|
157 | .. image:: ../images/saml-onelogin-attributes-example.png | |
105 | :alt: OneLogin SAML setup example |
|
158 | :alt: OneLogin SAML setup example | |
106 | :scale: 50 % No newline at end of file |
|
159 | :scale: 50 % |
@@ -1,34 +1,35 b'' | |||||
1 | .. _authentication-ref: |
|
1 | .. _authentication-ref: | |
2 |
|
2 | |||
3 | Authentication Options |
|
3 | Authentication Options | |
4 | ====================== |
|
4 | ====================== | |
5 |
|
5 | |||
6 | |RCE| provides a built in authentication against its own database. This is |
|
6 | |RCE| provides a built in authentication against its own database. This is | |
7 | implemented using ``RhodeCode Internal`` plugin. This plugin is enabled by default. |
|
7 | implemented using ``RhodeCode Internal`` plugin. This plugin is enabled by default. | |
8 | Additionally, |RCE| provides a Pluggable Authentication System. This gives the |
|
8 | Additionally, |RCE| provides a Pluggable Authentication System. This gives the | |
9 | administrator greater control over how users authenticate with the system. |
|
9 | administrator greater control over how users authenticate with the system. | |
10 |
|
10 | |||
11 | .. important:: |
|
11 | .. important:: | |
12 |
|
12 | |||
13 | You can disable the built in |RCE| authentication plugin |
|
13 | You can disable the built in |RCE| authentication plugin | |
14 | ``RhodeCode Internal`` and force all authentication to go |
|
14 | ``RhodeCode Internal`` and force all authentication to go | |
15 | through your authentication plugin of choice e.g LDAP only. |
|
15 | through your authentication plugin of choice e.g LDAP only. | |
16 | However, if you do this, and your external authentication tools fails, |
|
16 | However, if you do this, and your external authentication tools fails, | |
17 | accessing |RCE| will be blocked unless a fallback plugin is |
|
17 | accessing |RCE| will be blocked unless a fallback plugin is | |
18 | enabled via :file: rhodecode.ini |
|
18 | enabled via :file: rhodecode.ini | |
19 |
|
19 | |||
20 |
|
20 | |||
21 | |RCE| comes with the following user authentication management plugins: |
|
21 | |RCE| comes with the following user authentication management plugins: | |
22 |
|
22 | |||
23 |
|
23 | |||
24 | .. toctree:: |
|
24 | .. toctree:: | |
25 |
|
25 | |||
26 | auth-token |
|
26 | auth-token | |
27 | auth-ldap |
|
27 | auth-ldap | |
28 | auth-ldap-groups |
|
28 | auth-ldap-groups | |
29 | auth-saml-generic |
|
29 | auth-saml-generic | |
30 | auth-saml-onelogin |
|
30 | auth-saml-onelogin | |
31 | auth-saml-duosecurity |
|
31 | auth-saml-duosecurity | |
|
32 | auth-saml-azure | |||
32 | auth-crowd |
|
33 | auth-crowd | |
33 | auth-pam |
|
34 | auth-pam | |
34 | ssh-connection |
|
35 | ssh-connection |
@@ -1,153 +1,154 b'' | |||||
1 | # Copyright (C) 2012-2023 RhodeCode GmbH |
|
1 | # Copyright (C) 2012-2023 RhodeCode GmbH | |
2 | # |
|
2 | # | |
3 | # This program is free software: you can redistribute it and/or modify |
|
3 | # This program is free software: you can redistribute it and/or modify | |
4 | # it under the terms of the GNU Affero General Public License, version 3 |
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |
5 | # (only), as published by the Free Software Foundation. |
|
5 | # (only), as published by the Free Software Foundation. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU Affero General Public License |
|
12 | # You should have received a copy of the GNU Affero General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | # |
|
14 | # | |
15 | # This program is dual-licensed. If you wish to learn more about the |
|
15 | # This program is dual-licensed. If you wish to learn more about the | |
16 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
18 |
|
18 | |||
19 | import logging |
|
19 | import logging | |
20 | import collections |
|
20 | import collections | |
21 |
|
21 | |||
22 | from pyramid.exceptions import ConfigurationError |
|
22 | from pyramid.exceptions import ConfigurationError | |
23 |
|
23 | |||
24 | from rhodecode.lib.utils2 import safe_str |
|
24 | from rhodecode.lib.utils2 import safe_str | |
25 | from rhodecode.model.settings import SettingsModel |
|
25 | from rhodecode.model.settings import SettingsModel | |
26 | from rhodecode.translation import _ |
|
26 | from rhodecode.translation import _ | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | log = logging.getLogger(__name__) |
|
29 | log = logging.getLogger(__name__) | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | class AuthnResourceBase(object): |
|
32 | class AuthnResourceBase(object): | |
33 | __name__ = None |
|
33 | __name__ = None | |
34 | __parent__ = None |
|
34 | __parent__ = None | |
35 |
|
35 | |||
36 | def get_root(self): |
|
36 | def get_root(self): | |
37 | current = self |
|
37 | current = self | |
38 | while current.__parent__ is not None: |
|
38 | while current.__parent__ is not None: | |
39 | current = current.__parent__ |
|
39 | current = current.__parent__ | |
40 | return current |
|
40 | return current | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | class AuthnPluginResourceBase(AuthnResourceBase): |
|
43 | class AuthnPluginResourceBase(AuthnResourceBase): | |
44 |
|
44 | |||
45 | def __init__(self, plugin): |
|
45 | def __init__(self, plugin): | |
46 | self.plugin = plugin |
|
46 | self.plugin = plugin | |
47 | self.__name__ = plugin.get_url_slug() |
|
47 | self.__name__ = plugin.get_url_slug() | |
48 | self.display_name = plugin.get_display_name() |
|
48 | self.display_name = plugin.get_display_name() | |
49 |
|
49 | |||
50 |
|
50 | |||
51 | class AuthnRootResource(AuthnResourceBase): |
|
51 | class AuthnRootResource(AuthnResourceBase): | |
52 | """ |
|
52 | """ | |
53 | This is the root traversal resource object for the authentication settings. |
|
53 | This is the root traversal resource object for the authentication settings. | |
54 | """ |
|
54 | """ | |
|
55 | is_root = True | |||
55 |
|
56 | |||
56 | def __init__(self): |
|
57 | def __init__(self): | |
57 | self._store = collections.OrderedDict() |
|
58 | self._store = collections.OrderedDict() | |
58 | self._resource_name_map = {} |
|
59 | self._resource_name_map = {} | |
59 | self.display_name = _('Authentication Plugins') |
|
60 | self.display_name = _('Authentication Plugins') | |
60 |
|
61 | |||
61 | def __getitem__(self, key): |
|
62 | def __getitem__(self, key): | |
62 | """ |
|
63 | """ | |
63 | Customized get item function to return only items (plugins) that are |
|
64 | Customized get item function to return only items (plugins) that are | |
64 | activated. |
|
65 | activated. | |
65 | """ |
|
66 | """ | |
66 | if self._is_item_active(key): |
|
67 | if self._is_item_active(key): | |
67 | return self._store[key] |
|
68 | return self._store[key] | |
68 | else: |
|
69 | else: | |
69 | raise KeyError('Authentication plugin "{}" is not active.'.format( |
|
70 | raise KeyError('Authentication plugin "{}" is not active.'.format( | |
70 | key)) |
|
71 | key)) | |
71 |
|
72 | |||
72 | def __iter__(self): |
|
73 | def __iter__(self): | |
73 | for key in self._store.keys(): |
|
74 | for key in self._store.keys(): | |
74 | if self._is_item_active(key): |
|
75 | if self._is_item_active(key): | |
75 | yield self._store[key] |
|
76 | yield self._store[key] | |
76 |
|
77 | |||
77 | def _is_item_active(self, key): |
|
78 | def _is_item_active(self, key): | |
78 | activated_plugins = SettingsModel().get_auth_plugins() |
|
79 | activated_plugins = SettingsModel().get_auth_plugins() | |
79 | plugin_id = self.get_plugin_id(key) |
|
80 | plugin_id = self.get_plugin_id(key) | |
80 | return plugin_id in activated_plugins |
|
81 | return plugin_id in activated_plugins | |
81 |
|
82 | |||
82 | def get_plugin_id(self, resource_name): |
|
83 | def get_plugin_id(self, resource_name): | |
83 | """ |
|
84 | """ | |
84 | Return the plugin id for the given traversal resource name. |
|
85 | Return the plugin id for the given traversal resource name. | |
85 | """ |
|
86 | """ | |
86 | # TODO: Store this info in the resource element. |
|
87 | # TODO: Store this info in the resource element. | |
87 | return self._resource_name_map[resource_name] |
|
88 | return self._resource_name_map[resource_name] | |
88 |
|
89 | |||
89 | def get_sorted_list(self, sort_key=None): |
|
90 | def get_sorted_list(self, sort_key=None): | |
90 | """ |
|
91 | """ | |
91 | Returns a sorted list of sub resources for displaying purposes. |
|
92 | Returns a sorted list of sub resources for displaying purposes. | |
92 | """ |
|
93 | """ | |
93 | def default_sort_key(resource): |
|
94 | def default_sort_key(resource): | |
94 | return str.lower(safe_str(resource.display_name)) |
|
95 | return str.lower(safe_str(resource.display_name)) | |
95 |
|
96 | |||
96 | active = [item for item in self] |
|
97 | active = [item for item in self] | |
97 | return sorted(active, key=sort_key or default_sort_key) |
|
98 | return sorted(active, key=sort_key or default_sort_key) | |
98 |
|
99 | |||
99 | def get_nav_list(self, sort=True): |
|
100 | def get_nav_list(self, sort=True): | |
100 | """ |
|
101 | """ | |
101 | Returns a sorted list of resources for displaying the navigation. |
|
102 | Returns a sorted list of resources for displaying the navigation. | |
102 | """ |
|
103 | """ | |
103 | if sort: |
|
104 | if sort: | |
104 | nav_list = self.get_sorted_list() |
|
105 | nav_list = self.get_sorted_list() | |
105 | else: |
|
106 | else: | |
106 | nav_list = [item for item in self] |
|
107 | nav_list = [item for item in self] | |
107 |
|
108 | |||
108 | nav_list.insert(0, self) |
|
109 | nav_list.insert(0, self) | |
109 | return nav_list |
|
110 | return nav_list | |
110 |
|
111 | |||
111 | def add_authn_resource(self, config, plugin_id, resource): |
|
112 | def add_authn_resource(self, config, plugin_id, resource): | |
112 | """ |
|
113 | """ | |
113 | Register a traversal resource as a sub element to the authentication |
|
114 | Register a traversal resource as a sub element to the authentication | |
114 | settings. This method is registered as a directive on the pyramid |
|
115 | settings. This method is registered as a directive on the pyramid | |
115 | configurator object and called by plugins. |
|
116 | configurator object and called by plugins. | |
116 | """ |
|
117 | """ | |
117 |
|
118 | |||
118 | def _ensure_unique_name(name, limit=100): |
|
119 | def _ensure_unique_name(name, limit=100): | |
119 | counter = 1 |
|
120 | counter = 1 | |
120 | current = name |
|
121 | current = name | |
121 | while current in self._store.keys(): |
|
122 | while current in self._store.keys(): | |
122 | current = f'{name}{counter}' |
|
123 | current = f'{name}{counter}' | |
123 | counter += 1 |
|
124 | counter += 1 | |
124 | if counter > limit: |
|
125 | if counter > limit: | |
125 | raise ConfigurationError( |
|
126 | raise ConfigurationError( | |
126 | 'Cannot build unique name for traversal resource "%s" ' |
|
127 | 'Cannot build unique name for traversal resource "%s" ' | |
127 | 'registered by plugin "%s"', name, plugin_id) |
|
128 | 'registered by plugin "%s"', name, plugin_id) | |
128 | return current |
|
129 | return current | |
129 |
|
130 | |||
130 | # Allow plugin resources with identical names by rename duplicates. |
|
131 | # Allow plugin resources with identical names by rename duplicates. | |
131 | unique_name = _ensure_unique_name(resource.__name__) |
|
132 | unique_name = _ensure_unique_name(resource.__name__) | |
132 | if unique_name != resource.__name__: |
|
133 | if unique_name != resource.__name__: | |
133 | log.warning('Name collision for traversal resource "%s" registered ' |
|
134 | log.warning('Name collision for traversal resource "%s" registered ' | |
134 | 'by authentication plugin "%s"', resource.__name__, |
|
135 | 'by authentication plugin "%s"', resource.__name__, | |
135 | plugin_id) |
|
136 | plugin_id) | |
136 | resource.__name__ = unique_name |
|
137 | resource.__name__ = unique_name | |
137 |
|
138 | |||
138 | log.debug('Register traversal resource "%s" for plugin "%s"', |
|
139 | log.debug('Register traversal resource "%s" for plugin "%s"', | |
139 | unique_name, plugin_id) |
|
140 | unique_name, plugin_id) | |
140 | self._resource_name_map[unique_name] = plugin_id |
|
141 | self._resource_name_map[unique_name] = plugin_id | |
141 | resource.__parent__ = self |
|
142 | resource.__parent__ = self | |
142 | self._store[unique_name] = resource |
|
143 | self._store[unique_name] = resource | |
143 |
|
144 | |||
144 |
|
145 | |||
145 | root = AuthnRootResource() |
|
146 | root = AuthnRootResource() | |
146 |
|
147 | |||
147 |
|
148 | |||
148 | def root_factory(request=None): |
|
149 | def root_factory(request=None): | |
149 | """ |
|
150 | """ | |
150 | Returns the root traversal resource instance used for the authentication |
|
151 | Returns the root traversal resource instance used for the authentication | |
151 | settings route. |
|
152 | settings route. | |
152 | """ |
|
153 | """ | |
153 | return root |
|
154 | return root |
@@ -1,122 +1,127 b'' | |||||
1 | <%inherit file="/base/base.mako"/> |
|
1 | <%inherit file="/base/base.mako"/> | |
2 |
|
2 | |||
3 | <%def name="title()"> |
|
3 | <%def name="title()"> | |
4 | ${_('Authentication Settings')} |
|
4 | ${_('Authentication Settings')} | |
5 | %if c.rhodecode_name: |
|
5 | %if c.rhodecode_name: | |
6 | · ${h.branding(c.rhodecode_name)}} |
|
6 | · ${h.branding(c.rhodecode_name)}} | |
7 | %endif |
|
7 | %endif | |
8 | </%def> |
|
8 | </%def> | |
9 |
|
9 | |||
10 | <%def name="breadcrumbs_links()"></%def> |
|
10 | <%def name="breadcrumbs_links()"></%def> | |
11 |
|
11 | |||
12 | <%def name="menu_bar_nav()"> |
|
12 | <%def name="menu_bar_nav()"> | |
13 | ${self.menu_items(active='admin')} |
|
13 | ${self.menu_items(active='admin')} | |
14 | </%def> |
|
14 | </%def> | |
15 |
|
15 | |||
16 | <%def name="menu_bar_subnav()"> |
|
16 | <%def name="menu_bar_subnav()"> | |
17 | ${self.admin_menu(active='authentication')} |
|
17 | ${self.admin_menu(active='authentication')} | |
18 | </%def> |
|
18 | </%def> | |
19 |
|
19 | |||
20 | <%def name="main()"> |
|
20 | <%def name="main()"> | |
21 |
|
21 | |||
22 | <div class="box"> |
|
22 | <div class="box"> | |
23 |
|
23 | |||
24 | <div class='sidebar-col-wrapper'> |
|
24 | <div class='sidebar-col-wrapper'> | |
25 |
|
25 | |||
26 | <div class="sidebar"> |
|
26 | <div class="sidebar"> | |
27 | <ul class="nav nav-pills nav-stacked"> |
|
27 | <ul class="nav nav-pills nav-stacked"> | |
28 | % for item in resource.get_root().get_nav_list(): |
|
28 | % for item in resource.get_root().get_nav_list(): | |
|
29 | ||||
29 | <li ${('class=active' if item == resource else '')}> |
|
30 | <li ${('class=active' if item == resource else '')}> | |
30 | <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a> |
|
31 | % if getattr(item, 'is_root', False): | |
|
32 | <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a> | |||
|
33 | % else: | |||
|
34 | <a style="padding-left: 10px" href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a> | |||
|
35 | % endif | |||
31 | </li> |
|
36 | </li> | |
32 | % endfor |
|
37 | % endfor | |
33 | </ul> |
|
38 | </ul> | |
34 | </div> |
|
39 | </div> | |
35 |
|
40 | |||
36 | <div class="main-content-full-width"> |
|
41 | <div class="main-content-full-width"> | |
37 | ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)} |
|
42 | ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)} | |
38 | <div class="panel panel-default"> |
|
43 | <div class="panel panel-default"> | |
39 |
|
44 | |||
40 | <div class="panel-heading"> |
|
45 | <div class="panel-heading"> | |
41 | <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3> |
|
46 | <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3> | |
42 | </div> |
|
47 | </div> | |
43 |
|
48 | |||
44 | <div class="panel-body"> |
|
49 | <div class="panel-body"> | |
45 |
|
50 | |||
46 |
|
51 | |||
47 | <div class="label">${_("Ordered Activated Plugins")}</div> |
|
52 | <div class="label">${_("Ordered Activated Plugins")}</div> | |
48 | <div class="textarea text-area editor"> |
|
53 | <div class="textarea text-area editor"> | |
49 | ${h.textarea('auth_plugins',cols=120,rows=20,class_="medium")} |
|
54 | ${h.textarea('auth_plugins',cols=120,rows=20,class_="medium")} | |
50 | </div> |
|
55 | </div> | |
51 | <div class="field"> |
|
56 | <div class="field"> | |
52 | <p class="help-block pre-formatting">${_('List of plugins, separated by commas.' |
|
57 | <p class="help-block pre-formatting">${_('List of plugins, separated by commas.' | |
53 | '\nThe order of the plugins is also the order in which ' |
|
58 | '\nThe order of the plugins is also the order in which ' | |
54 | 'RhodeCode Enterprise will try to authenticate a user.')} |
|
59 | 'RhodeCode Enterprise will try to authenticate a user.')} | |
55 | </p> |
|
60 | </p> | |
56 | </div> |
|
61 | </div> | |
57 |
|
62 | |||
58 | <table class="rctable"> |
|
63 | <table class="rctable"> | |
59 | <th>${_('Activate')}</th> |
|
64 | <th>${_('Activate')}</th> | |
60 | <th>${_('Plugin Name')}</th> |
|
65 | <th>${_('Plugin Name')}</th> | |
61 | <th>${_('Documentation')}</th> |
|
66 | <th>${_('Documentation')}</th> | |
62 | <th>${_('Plugin ID')}</th> |
|
67 | <th>${_('Plugin ID')}</th> | |
63 | <th>${_('Enabled')}</th> |
|
68 | <th>${_('Enabled')}</th> | |
64 | %for plugin in available_plugins: |
|
69 | %for plugin in available_plugins: | |
65 | <tr class="${('inactive' if (not plugin.is_active() and plugin.get_id() in enabled_plugins) else '')}"> |
|
70 | <tr class="${('inactive' if (not plugin.is_active() and plugin.get_id() in enabled_plugins) else '')}"> | |
66 | <td> |
|
71 | <td> | |
67 | <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${('btn-success' if plugin.get_id() in enabled_plugins else '')}"> |
|
72 | <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${('btn-success' if plugin.get_id() in enabled_plugins else '')}"> | |
68 | ${(_('activated') if plugin.get_id() in enabled_plugins else _('not active'))} |
|
73 | ${(_('activated') if plugin.get_id() in enabled_plugins else _('not active'))} | |
69 | </span> |
|
74 | </span> | |
70 | </td> |
|
75 | </td> | |
71 | <td>${plugin.get_display_name()}</td> |
|
76 | <td>${plugin.get_display_name()}</td> | |
72 | <td> |
|
77 | <td> | |
73 | % if plugin.docs(): |
|
78 | % if plugin.docs(): | |
74 | <a href="${plugin.docs()}">docs</a> |
|
79 | <a href="${plugin.docs()}">docs</a> | |
75 | % endif |
|
80 | % endif | |
76 | </td> |
|
81 | </td> | |
77 | <td>${plugin.get_id()}</td> |
|
82 | <td>${plugin.get_id()}</td> | |
78 | <td>${h.bool2icon(plugin.is_active(),show_at_false=False)}</td> |
|
83 | <td>${h.bool2icon(plugin.is_active(),show_at_false=False)}</td> | |
79 | </tr> |
|
84 | </tr> | |
80 | %endfor |
|
85 | %endfor | |
81 | </table> |
|
86 | </table> | |
82 |
|
87 | |||
83 | <div class="buttons"> |
|
88 | <div class="buttons"> | |
84 | ${h.submit('save',_('Save'),class_="btn")} |
|
89 | ${h.submit('save',_('Save'),class_="btn")} | |
85 | </div> |
|
90 | </div> | |
86 | </div> |
|
91 | </div> | |
87 | </div> |
|
92 | </div> | |
88 | ${h.end_form()} |
|
93 | ${h.end_form()} | |
89 | </div> |
|
94 | </div> | |
90 | </div> |
|
95 | </div> | |
91 | </div> |
|
96 | </div> | |
92 |
|
97 | |||
93 | <script> |
|
98 | <script> | |
94 | $('.toggle-plugin').click(function(e){ |
|
99 | $('.toggle-plugin').click(function(e){ | |
95 | var auth_plugins_input = $('#auth_plugins'); |
|
100 | var auth_plugins_input = $('#auth_plugins'); | |
96 | var elems = []; |
|
101 | var elems = []; | |
97 |
|
102 | |||
98 | $.each(auth_plugins_input.val().split(',') , function (index, element) { |
|
103 | $.each(auth_plugins_input.val().split(',') , function (index, element) { | |
99 | if (element !== "") { |
|
104 | if (element !== "") { | |
100 | elems.push(element.strip()) |
|
105 | elems.push(element.strip()) | |
101 | } |
|
106 | } | |
102 | }); |
|
107 | }); | |
103 |
|
108 | |||
104 | var cur_button = e.currentTarget; |
|
109 | var cur_button = e.currentTarget; | |
105 | var plugin_id = $(cur_button).attr('plugin_id'); |
|
110 | var plugin_id = $(cur_button).attr('plugin_id'); | |
106 | if($(cur_button).hasClass('btn-success')){ |
|
111 | if($(cur_button).hasClass('btn-success')){ | |
107 | elems.splice(elems.indexOf(plugin_id), 1); |
|
112 | elems.splice(elems.indexOf(plugin_id), 1); | |
108 | auth_plugins_input.val(elems.join(',\n')); |
|
113 | auth_plugins_input.val(elems.join(',\n')); | |
109 | $(cur_button).removeClass('btn-success'); |
|
114 | $(cur_button).removeClass('btn-success'); | |
110 | cur_button.innerHTML = _gettext('not active'); |
|
115 | cur_button.innerHTML = _gettext('not active'); | |
111 | } |
|
116 | } | |
112 | else{ |
|
117 | else{ | |
113 | if (elems.indexOf(plugin_id) === -1) { |
|
118 | if (elems.indexOf(plugin_id) === -1) { | |
114 | elems.push(plugin_id); |
|
119 | elems.push(plugin_id); | |
115 | } |
|
120 | } | |
116 | auth_plugins_input.val(elems.join(',\n')); |
|
121 | auth_plugins_input.val(elems.join(',\n')); | |
117 | $(cur_button).addClass('btn-success'); |
|
122 | $(cur_button).addClass('btn-success'); | |
118 | cur_button.innerHTML = _gettext('activated'); |
|
123 | cur_button.innerHTML = _gettext('activated'); | |
119 | } |
|
124 | } | |
120 | }); |
|
125 | }); | |
121 | </script> |
|
126 | </script> | |
122 | </%def> |
|
127 | </%def> |
@@ -1,139 +1,146 b'' | |||||
1 | <%inherit file="/base/base.mako"/> |
|
1 | <%inherit file="/base/base.mako"/> | |
2 |
|
2 | |||
3 | <%def name="title()"> |
|
3 | <%def name="title()"> | |
4 | ${_('Authentication Settings')} |
|
4 | ${_('Authentication Settings')} | |
5 | %if c.rhodecode_name: |
|
5 | %if c.rhodecode_name: | |
6 | · ${h.branding(c.rhodecode_name)}} |
|
6 | · ${h.branding(c.rhodecode_name)}} | |
7 | %endif |
|
7 | %endif | |
8 | </%def> |
|
8 | </%def> | |
9 |
|
9 | |||
10 | <%def name="breadcrumbs_links()"> |
|
10 | <%def name="breadcrumbs_links()"> | |
11 | ${h.link_to(_('Admin'),h.route_path('admin_home'))} |
|
11 | ${h.link_to(_('Admin'),h.route_path('admin_home'))} | |
12 | » |
|
12 | » | |
13 | ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))} |
|
13 | ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))} | |
14 | » |
|
14 | » | |
15 | ${resource.display_name} |
|
15 | ${resource.display_name} | |
16 | </%def> |
|
16 | </%def> | |
17 |
|
17 | |||
18 | <%def name="menu_bar_nav()"> |
|
18 | <%def name="menu_bar_nav()"> | |
19 | ${self.menu_items(active='admin')} |
|
19 | ${self.menu_items(active='admin')} | |
20 | </%def> |
|
20 | </%def> | |
21 |
|
21 | |||
22 | <%def name="menu_bar_subnav()"> |
|
22 | <%def name="menu_bar_subnav()"> | |
23 | ${self.admin_menu(active='authentication')} |
|
23 | ${self.admin_menu(active='authentication')} | |
24 | </%def> |
|
24 | </%def> | |
25 |
|
25 | |||
26 | <%def name="main()"> |
|
26 | <%def name="main()"> | |
27 |
|
27 | |||
28 | <div class="box"> |
|
28 | <div class="box"> | |
29 |
|
29 | |||
30 | <div class='sidebar-col-wrapper'> |
|
30 | <div class='sidebar-col-wrapper'> | |
31 |
|
31 | |||
32 | <div class="sidebar"> |
|
32 | <div class="sidebar"> | |
33 | <ul class="nav nav-pills nav-stacked"> |
|
33 | <ul class="nav nav-pills nav-stacked"> | |
34 | % for item in resource.get_root().get_nav_list(): |
|
34 | % for item in resource.get_root().get_nav_list(): | |
35 | <li ${('class=active' if item == resource else '')}> |
|
35 | <li ${('class=active' if item == resource else '')}> | |
36 | <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a> |
|
36 | <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a> | |
37 | </li> |
|
37 | </li> | |
38 | % endfor |
|
38 | % endfor | |
39 | </ul> |
|
39 | </ul> | |
40 | </div> |
|
40 | </div> | |
41 |
|
41 | |||
42 | <div class="main-content-full-width"> |
|
42 | <div class="main-content-full-width"> | |
43 | <div class="panel panel-default"> |
|
43 | <div class="panel panel-default"> | |
44 | <div class="panel-heading"> |
|
44 | <div class="panel-heading"> | |
45 | <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3> |
|
45 | <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3> | |
46 | </div> |
|
46 | </div> | |
47 | <div class="panel-body"> |
|
47 | <div class="panel-body"> | |
48 | <div class="plugin_form"> |
|
48 | <div class="plugin_form"> | |
49 | <div class="fields"> |
|
49 | <div class="fields"> | |
50 | ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)} |
|
50 | ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)} | |
51 | <div class="form"> |
|
51 | <div class="form"> | |
52 |
|
|
52 | ||
|
53 | ## Allow derived templates to add something above the form | |||
|
54 | ## input fields | |||
|
55 | %if hasattr(next, 'above_form_fields'): | |||
|
56 | ${next.above_form_fields()} | |||
|
57 | %endif | |||
|
58 | ||||
|
59 | <h4>${_('Plugin Configuration')}</h4> | |||
53 | %for node in plugin.get_settings_schema(): |
|
60 | %for node in plugin.get_settings_schema(): | |
54 | <% |
|
61 | <% | |
55 | label_to_type = {'label-checkbox': 'bool', 'label-textarea': 'textarea'} |
|
62 | label_to_type = {'label-checkbox': 'bool', 'label-textarea': 'textarea'} | |
56 | %> |
|
63 | %> | |
57 |
|
64 | |||
58 | <div class="field"> |
|
65 | <div class="field"> | |
59 | <div class="label ${label_to_type.get(node.widget)}"><label for="${node.name}">${node.title}</label></div> |
|
66 | <div class="label ${label_to_type.get(node.widget)}"><label for="${node.name}">${node.title}</label></div> | |
60 | <div class="input"> |
|
67 | <div class="input"> | |
61 | %if node.widget in ["string", "int", "unicode"]: |
|
68 | %if node.widget in ["string", "int", "unicode"]: | |
62 | ${h.text(node.name, defaults.get(node.name), class_="large")} |
|
69 | ${h.text(node.name, defaults.get(node.name), class_="large")} | |
63 | %elif node.widget == "password": |
|
70 | %elif node.widget == "password": | |
64 | ${h.password(node.name, defaults.get(node.name), class_="large")} |
|
71 | ${h.password(node.name, defaults.get(node.name), class_="large")} | |
65 | %elif node.widget == "bool": |
|
72 | %elif node.widget == "bool": | |
66 | %if node.name == "global_2fa" and c.rhodecode_edition_id != "EE": |
|
73 | %if node.name == "global_2fa" and c.rhodecode_edition_id != "EE": | |
67 | <input type="checkbox" disabled/> |
|
74 | <input type="checkbox" disabled/> | |
68 | <%node.description = _('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')%> |
|
75 | <%node.description = _('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')%> | |
69 | %else: |
|
76 | %else: | |
70 | <div class="checkbox" >${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div> |
|
77 | <div class="checkbox" >${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div> | |
71 | %endif |
|
78 | %endif | |
72 | %elif node.widget == "select": |
|
79 | %elif node.widget == "select": | |
73 | ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")} |
|
80 | ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")} | |
74 | %elif node.widget == "select_with_labels": |
|
81 | %elif node.widget == "select_with_labels": | |
75 | ${h.select(node.name, defaults.get(node.name), node.choices, class_="select2AuthSetting")} |
|
82 | ${h.select(node.name, defaults.get(node.name), node.choices, class_="select2AuthSetting")} | |
76 | %elif node.widget == "textarea": |
|
83 | %elif node.widget == "textarea": | |
77 | <div class="textarea" style="margin-left: 0px">${h.textarea(node.name, defaults.get(node.name), rows=10)}</div> |
|
84 | <div class="textarea" style="margin-left: 0px">${h.textarea(node.name, defaults.get(node.name), rows=10)}</div> | |
78 | %elif node.widget == "readonly": |
|
85 | %elif node.widget == "readonly": | |
79 | ${node.default} |
|
86 | ${node.default} | |
80 | %else: |
|
87 | %else: | |
81 | This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select]. |
|
88 | This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select]. | |
82 | %endif |
|
89 | %endif | |
83 |
|
90 | |||
84 | %if node.name in errors: |
|
91 | %if node.name in errors: | |
85 | <span class="error-message">${errors.get(node.name)}</span> |
|
92 | <span class="error-message">${errors.get(node.name)}</span> | |
86 | <br /> |
|
93 | <br /> | |
87 | %endif |
|
94 | %endif | |
88 | <p class="help-block pre-formatting">${node.description | n}</p> |
|
95 | <p class="help-block pre-formatting">${node.description | n}</p> | |
89 | </div> |
|
96 | </div> | |
90 | </div> |
|
97 | </div> | |
91 | %endfor |
|
98 | %endfor | |
92 |
|
99 | |||
93 | ## Allow derived templates to add something below the form |
|
100 | ## Allow derived templates to add something below the form | |
94 | ## input fields |
|
101 | ## input fields | |
95 | %if hasattr(next, 'below_form_fields'): |
|
102 | %if hasattr(next, 'below_form_fields'): | |
96 | ${next.below_form_fields()} |
|
103 | ${next.below_form_fields()} | |
97 | %endif |
|
104 | %endif | |
98 |
|
105 | |||
99 | <div class="buttons"> |
|
106 | <div class="buttons"> | |
100 | ${h.submit('save',_('Save'),class_="btn")} |
|
107 | ${h.submit('save',_('Save'),class_="btn")} | |
101 | </div> |
|
108 | </div> | |
102 |
|
109 | |||
103 | </div> |
|
110 | </div> | |
104 | ${h.end_form()} |
|
111 | ${h.end_form()} | |
105 | </div> |
|
112 | </div> | |
106 | </div> |
|
113 | </div> | |
107 |
|
114 | |||
108 | % if request.GET.get('schema'): |
|
115 | % if request.GET.get('schema'): | |
109 | ## this is for development and creation of example configurations for documentation |
|
116 | ## this is for development and creation of example configurations for documentation | |
110 | <pre> |
|
117 | <pre> | |
111 | % for node in plugin.get_settings_schema(): |
|
118 | % for node in plugin.get_settings_schema(): | |
112 | *option*: `${node.name}` => `${defaults.get(node.name)}`${'\n # '.join(['']+node.description.splitlines())} |
|
119 | *option*: `${node.name}` => `${defaults.get(node.name)}`${'\n # '.join(['']+node.description.splitlines())} | |
113 |
|
120 | |||
114 | % endfor |
|
121 | % endfor | |
115 | </pre> |
|
122 | </pre> | |
116 |
|
123 | |||
117 | % endif |
|
124 | % endif | |
118 |
|
125 | |||
119 | </div> |
|
126 | </div> | |
120 | </div> |
|
127 | </div> | |
121 | </div> |
|
128 | </div> | |
122 |
|
129 | |||
123 | </div> |
|
130 | </div> | |
124 | </div> |
|
131 | </div> | |
125 |
|
132 | |||
126 |
|
133 | |||
127 | <script> |
|
134 | <script> | |
128 | $(document).ready(function() { |
|
135 | $(document).ready(function() { | |
129 | var select2Options = { |
|
136 | var select2Options = { | |
130 | containerCssClass: 'drop-menu', |
|
137 | containerCssClass: 'drop-menu', | |
131 | dropdownCssClass: 'drop-menu-dropdown', |
|
138 | dropdownCssClass: 'drop-menu-dropdown', | |
132 | dropdownAutoWidth: true, |
|
139 | dropdownAutoWidth: true, | |
133 | minimumResultsForSearch: -1 |
|
140 | minimumResultsForSearch: -1 | |
134 | }; |
|
141 | }; | |
135 | $('.select2AuthSetting').select2(select2Options); |
|
142 | $('.select2AuthSetting').select2(select2Options); | |
136 |
|
143 | |||
137 | }); |
|
144 | }); | |
138 | </script> |
|
145 | </script> | |
139 | </%def> |
|
146 | </%def> |
General Comments 0
You need to be logged in to leave comments.
Login now