{
    "componentChunkName": "component---src-templates-plugin-tsx",
    "path": "/plugins/scm-oauth2-plugin",
    "result": {"data":{"plugin":{"name":"scm-oauth2-plugin","displayName":"OAuth2/OIDC","description":"Authentication for SCM-Manager using OAuth2/OIDC"},"readme":{"html":"<p align=\"center\">\n  <a href=\"https://scm-manager.org/\">\n    <img alt=\"SCM-Manager\" src=\"https://download.scm-manager.org/images/logo/scm-manager_logo.png\" width=\"500\">\n  </a>\n</p>\n<h1 align=\"center\">\n  OAuth2/OIDC Authentication Plugin\n</h1>\n<p><em><a href=\"README_de.md\">Deutsche Fassung</a></em></p>\n<p>SSO authentication for <a href=\"https://scm-manager.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">SCM-Manager</a> (3.x) via OAuth2 / OpenID Connect\nusing the authorization code flow. Tested with <a href=\"https://www.keycloak.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Keycloak</a>, but works with\nany spec compliant identity provider (IdP).</p>\n<h2 id=\"features\" style=\"position:relative;\"><a href=\"#features\" aria-label=\"features permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Features</h2>\n<ul>\n<li><strong>Authorization code flow</strong> with a confidential client (client id + client secret) and\n<strong>PKCE</strong> (S256)</li>\n<li><strong>OIDC discovery</strong>: all endpoints can be resolved automatically from the\n<code class=\"language-text\">.well-known/openid-configuration</code> document of the IdP — alternatively all endpoints can be\nconfigured manually</li>\n<li><strong>Login button</strong> on the login page: \"Login with &#x3C;provider name>\", in addition to the\nconventional username/password form</li>\n<li><strong>User synchronization</strong>: users are created as <em>external</em> users on first login; display name and\nemail are updated from the configured userinfo claims on every login</li>\n<li><strong>Migration of existing users</strong>: an instance which already authenticates against LDAP (or any\nother source) can be switched over without losing permissions, ownership or memberships, as long\nas the IdP delivers the same user names</li>\n<li><strong>Group synchronization</strong>: groups from the group claim are created as SCM groups if missing and\nthe user is added to/removed from them on every login — groups are never deleted</li>\n<li><strong>Roles from the access token</strong> (optional): read additional roles from a configurable JSON path\ninside the access token, e.g. the Keycloak realm roles at <code class=\"language-text\">realm_access.roles</code></li>\n<li><strong>Administrator group</strong>: members of a configurable group get the global administrator\npermission assigned on login (and revoked again, if the group membership is gone)</li>\n<li><strong>Force login</strong> (optional): unauthenticated browser requests are redirected to the IdP\nautomatically, conventional login is disabled</li>\n<li><strong>Full SSO logout</strong> (optional): RP-initiated logout at the IdP with <code class=\"language-text\">id_token_hint</code> and\n<code class=\"language-text\">post_logout_redirect_uri</code>, so the complete SSO session ends without a confirmation prompt and\nthe browser returns to the SCM login page</li>\n<li><strong>Verified tokens</strong>: signature and claim verification of id token and access token against the\nJSON Web Key Set of the IdP, including a nonce per login</li>\n<li>The client secret is stored <strong>encrypted</strong> and is never returned by the API</li>\n</ul>\n<h2 id=\"installation\" style=\"position:relative;\"><a href=\"#installation\" aria-label=\"installation permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Installation</h2>\n<p>The plugin is not part of the official plugin center and is therefore installed manually. It\nrequires <strong>SCM-Manager 3.9.0 or newer</strong>.</p>\n<ol>\n<li>\n<p>Download <code class=\"language-text\">scm-oauth2-plugin.smp</code> from the\n<a href=\"https://github.com/thorsten-l/scm-oauth2-plugin/releases\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">releases page</a></p>\n</li>\n<li>\n<p>Copy the file into the <code class=\"language-text\">plugins</code> directory of your SCM-Manager home, e.g.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">cp</span> scm-oauth2-plugin.smp /var/lib/scm/plugins/</code></pre></div>\n</li>\n<li>\n<p>Restart SCM-Manager. The archive is unpacked during startup — afterwards the directory\n<code class=\"language-text\">plugins/scm-oauth2-plugin/</code> exists and the <code class=\"language-text\">.smp</code> file is gone, which is expected.</p>\n</li>\n<li>\n<p>Verify the installation in <em>Administration → Plugins → Installed</em>, or via REST:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">curl</span> <span class=\"token parameter variable\">-u</span> <span class=\"token string\">\"scmadmin:secret\"</span> http://localhost:8080/scm/api/v2/plugins/installed <span class=\"token punctuation\">\\</span>\n  <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token parameter variable\">-o</span> <span class=\"token string\">'\"name\":\"scm-oauth2-plugin\",\"version\":\"[^\"]*\"'</span></code></pre></div>\n</li>\n</ol>\n<p>Afterwards configure the plugin as described under <a href=\"#configuration\">Configuration</a>.</p>\n<h3 id=\"finding-the-home-directory\" style=\"position:relative;\"><a href=\"#finding-the-home-directory\" aria-label=\"finding the home directory permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Finding the home directory</h3>\n<p>The home directory is the one containing <code class=\"language-text\">config/</code>, <code class=\"language-text\">repositories/</code> and <code class=\"language-text\">plugins/</code>. The reliable\nway to find it is the log entry written on every startup:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">grep</span> <span class=\"token string\">\"scm home directory\"</span> /var/log/scm/scm-manager.log\n<span class=\"token comment\"># using directory /var/lib/scm as scm home directory</span></code></pre></div>\n<p>Typical defaults, unless overridden by the environment variable <code class=\"language-text\">SCM_HOME</code> or the system property\n<code class=\"language-text\">scm.home</code>:</p>\n<table>\n<thead>\n<tr>\n<th>Installation</th>\n<th>Home directory</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Linux package / Docker</td>\n<td><code class=\"language-text\">/var/lib/scm</code></td>\n</tr>\n<tr>\n<td>macOS</td>\n<td><code class=\"language-text\">~/Library/Application Support/SCM-Manager</code></td>\n</tr>\n<tr>\n<td>Windows</td>\n<td><code class=\"language-text\">%APPDATA%\\SCM-Manager</code></td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"update\" style=\"position:relative;\"><a href=\"#update\" aria-label=\"update permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Update</h3>\n<p>Since the archive is unpacked, an old version has to be removed first, otherwise the previous\nplugin directory stays in place:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># stop SCM-Manager first</span>\n<span class=\"token function\">rm</span> <span class=\"token parameter variable\">-rf</span> /var/lib/scm/plugins/scm-oauth2-plugin\n<span class=\"token function\">cp</span> scm-oauth2-plugin.smp /var/lib/scm/plugins/\n<span class=\"token comment\"># start SCM-Manager</span></code></pre></div>\n<p>The configuration is stored in <code class=\"language-text\">config/oauth2.xml</code> and survives an update. To uninstall, remove the\nplugin directory and restart.</p>\n<h2 id=\"how-it-works\" style=\"position:relative;\"><a href=\"#how-it-works\" aria-label=\"how it works permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>How it works</h2>\n<h3 id=\"login\" style=\"position:relative;\"><a href=\"#login\" aria-label=\"login permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Login</h3>\n<ol>\n<li>The user clicks \"Login with &#x3C;provider>\" (or is redirected by the force login filter) —\n<code class=\"language-text\">GET /api/v2/oauth2/auth?from=&lt;original url></code></li>\n<li>The plugin creates a random <code class=\"language-text\">state</code> (CSRF protection, valid once, 10 minutes), a PKCE verifier\nand a nonce, stores the <code class=\"language-text\">state</code> in an <code class=\"language-text\">HttpOnly</code> cookie of this browser and redirects to the\nauthorization endpoint of the IdP (with <code class=\"language-text\">code_challenge</code>, if the IdP supports S256)</li>\n<li>After authentication the IdP redirects back to the callback\n<code class=\"language-text\">GET /api/v2/oauth2/auth/callback?code=...&amp;state=...</code></li>\n<li>The plugin checks that the <code class=\"language-text\">state</code> belongs to the state cookie of this browser, exchanges the\nauthorization code for tokens at the token endpoint (server-to-server, <code class=\"language-text\">client_secret_post</code>,\nwith <code class=\"language-text\">code_verifier</code>), <strong>verifies the id token</strong> (signature, issuer, audience, expiry, nonce) and\nfetches the user claims from the <strong>userinfo endpoint</strong></li>\n<li>The user is created/updated/migrated, groups and permissions are synchronized, the id token is\nkept in memory for a later SSO logout and an SCM access token cookie is issued</li>\n<li>The browser is redirected to the originally requested url</li>\n</ol>\n<blockquote>\n<p><strong>Important:</strong> the plugin reads the claims (username, display name, email, groups) from the\n<strong>userinfo endpoint</strong> — not from the access or id token. Make sure your IdP adds the claims to\nthe userinfo response (in Keycloak: protocol mapper setting <em>Add to userinfo</em>). The only\nexception is the optional <a href=\"#roles-from-the-access-token\">role import</a> from the access token.</p>\n</blockquote>\n<h3 id=\"logout-optional-ssologout\" style=\"position:relative;\"><a href=\"#logout-optional-ssologout\" aria-label=\"logout optional ssologout permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Logout (optional, <code class=\"language-text\">ssoLogout</code>)</h3>\n<p>On logout SCM-Manager redirects the browser to the end session endpoint of the IdP with:</p>\n<ul>\n<li><code class=\"language-text\">id_token_hint</code> — the id token of the login (kept in memory per user, consumed on logout), so\nthe IdP can end the matching SSO session without asking for confirmation</li>\n<li><code class=\"language-text\">client_id</code></li>\n<li><code class=\"language-text\">post_logout_redirect_uri</code> — the SCM-Manager base url, so the browser returns to the SCM login\npage afterwards; <strong>must be registered at the IdP client</strong> as a valid post logout redirect uri</li>\n</ul>\n<p>If the id token is not available (e.g. after a server restart), the logout still works — the IdP\nmay then show a confirmation page.</p>\n<h2 id=\"configuration\" style=\"position:relative;\"><a href=\"#configuration\" aria-label=\"configuration permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Configuration</h2>\n<p>Administration → Settings → OAuth2 / OIDC, or via REST (see below).</p>\n<table>\n<thead>\n<tr>\n<th>Setting</th>\n<th>Key</th>\n<th>Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Enabled</td>\n<td><code class=\"language-text\">enabled</code></td>\n<td><code class=\"language-text\">false</code></td>\n<td>Master switch for the plugin</td>\n</tr>\n<tr>\n<td>Force login</td>\n<td><code class=\"language-text\">forceLogin</code></td>\n<td><code class=\"language-text\">false</code></td>\n<td>Redirect unauthenticated browsers to the IdP automatically; conventional login is no longer possible</td>\n</tr>\n<tr>\n<td>Full SSO logout</td>\n<td><code class=\"language-text\">ssoLogout</code></td>\n<td><code class=\"language-text\">false</code></td>\n<td>RP-initiated logout at the IdP (see above)</td>\n</tr>\n<tr>\n<td>Take over local accounts</td>\n<td><code class=\"language-text\">migrateLocalUsers</code></td>\n<td><code class=\"language-text\">false</code></td>\n<td>Allows the IdP to take over accounts which still have a local password, see <a href=\"#migration-of-existing-users\">Migration of existing users</a></td>\n</tr>\n<tr>\n<td>Provider name</td>\n<td><code class=\"language-text\">providerName</code></td>\n<td>—</td>\n<td>Display name of the IdP, shown as \"Login with &#x3C;provider name>\". <strong>Required</strong> if the plugin is enabled</td>\n</tr>\n<tr>\n<td>OIDC Discovery URL</td>\n<td><code class=\"language-text\">discoveryUrl</code></td>\n<td>—</td>\n<td>Issuer url or complete url of the <code class=\"language-text\">.well-known/openid-configuration</code> document. If set, all endpoints below are resolved automatically (cached for one hour) and the manual entries are ignored</td>\n</tr>\n<tr>\n<td>Authorization endpoint</td>\n<td><code class=\"language-text\">authorizationUrl</code></td>\n<td>—</td>\n<td>Manual endpoint configuration (only used without discovery url)</td>\n</tr>\n<tr>\n<td>Token endpoint</td>\n<td><code class=\"language-text\">tokenUrl</code></td>\n<td>—</td>\n<td>\"</td>\n</tr>\n<tr>\n<td>Userinfo endpoint</td>\n<td><code class=\"language-text\">userinfoUrl</code></td>\n<td>—</td>\n<td>\"</td>\n</tr>\n<tr>\n<td>End session endpoint</td>\n<td><code class=\"language-text\">endSessionUrl</code></td>\n<td>—</td>\n<td>\", optional; only needed for <code class=\"language-text\">ssoLogout</code></td>\n</tr>\n<tr>\n<td>JWKS endpoint</td>\n<td><code class=\"language-text\">jwksUrl</code></td>\n<td>—</td>\n<td>\", public keys used to verify the signatures of id token and access token. Without it (and without a discovery url) no token is verified and none is used</td>\n</tr>\n<tr>\n<td>Client ID</td>\n<td><code class=\"language-text\">clientId</code></td>\n<td>—</td>\n<td>Client registered at the IdP</td>\n</tr>\n<tr>\n<td>Client secret</td>\n<td><code class=\"language-text\">clientSecret</code></td>\n<td>—</td>\n<td>Secret of the (confidential) client. Stored encrypted, never returned by <code class=\"language-text\">GET</code>; send an empty value on update to keep the stored secret (the read-only flag <code class=\"language-text\">clientSecretSet</code> tells whether one is stored)</td>\n</tr>\n<tr>\n<td>Scopes</td>\n<td><code class=\"language-text\">scopes</code></td>\n<td><code class=\"language-text\">openid profile email</code></td>\n<td>Space separated list of requested scopes</td>\n</tr>\n<tr>\n<td>Username claim</td>\n<td><code class=\"language-text\">usernameAttribute</code></td>\n<td><code class=\"language-text\">preferred_username</code></td>\n<td>Userinfo claim used as SCM username (falls back to <code class=\"language-text\">sub</code>)</td>\n</tr>\n<tr>\n<td>Display name claim</td>\n<td><code class=\"language-text\">displayNameAttribute</code></td>\n<td><code class=\"language-text\">name</code></td>\n<td>Userinfo claim for the display name</td>\n</tr>\n<tr>\n<td>Mail claim</td>\n<td><code class=\"language-text\">mailAttribute</code></td>\n<td><code class=\"language-text\">email</code></td>\n<td>Userinfo claim for the email address (invalid addresses are ignored)</td>\n</tr>\n<tr>\n<td>Group claim</td>\n<td><code class=\"language-text\">groupAttribute</code></td>\n<td><code class=\"language-text\">groups</code></td>\n<td>Userinfo claim for group memberships (string or array of strings)</td>\n</tr>\n<tr>\n<td>Import realm roles from access token</td>\n<td><code class=\"language-text\">importRealmRoles</code></td>\n<td><code class=\"language-text\">false</code></td>\n<td>Additionally read roles from the access token, see below</td>\n</tr>\n<tr>\n<td>JSON path of the roles</td>\n<td><code class=\"language-text\">realmRolesPath</code></td>\n<td><code class=\"language-text\">realm_access.roles</code></td>\n<td>Path to the roles inside the access token, field names separated by dots</td>\n</tr>\n<tr>\n<td>Administrator group</td>\n<td><code class=\"language-text\">adminGroup</code></td>\n<td><code class=\"language-text\">scmadmin</code></td>\n<td>Members of this group get the global administrator permission on login, non-members get it revoked. Empty = permissions are never touched</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"redirect-uri\" style=\"position:relative;\"><a href=\"#redirect-uri\" aria-label=\"redirect uri permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Redirect URI</h3>\n<p>Register the following redirect uri at the IdP client:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">&lt;scm-base-url>/api/v2/oauth2/auth/callback</code></pre></div>\n<p>e.g. <code class=\"language-text\">http://scm-server.localhost:8080/scm/api/v2/oauth2/auth/callback</code>. For the SSO logout the\nSCM base url (e.g. <code class=\"language-text\">http://scm-server.localhost:8080/scm</code>) has to be registered as valid <strong>post\nlogout redirect uri</strong> additionally.</p>\n<h3 id=\"configuration-via-rest\" style=\"position:relative;\"><a href=\"#configuration-via-rest\" aria-label=\"configuration via rest permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Configuration via REST</h3>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">curl</span> <span class=\"token parameter variable\">-u</span> <span class=\"token string\">\"scmadmin:secret\"</span> <span class=\"token parameter variable\">-H</span> <span class=\"token string\">\"Content-Type: application/vnd.scmm-oauth2Config+json;v=2\"</span> <span class=\"token parameter variable\">-XPUT</span> <span class=\"token parameter variable\">-d</span> <span class=\"token string\">'{\n  \"providerName\": \"Sonia\",\n  \"discoveryUrl\": \"https://id.dev.sonia.de/realms/sonia\",\n  \"clientId\": \"scm-server\",\n  \"clientSecret\": \"&lt;secret>\",\n  \"scopes\": \"openid profile email\",\n  \"usernameAttribute\": \"preferred_username\",\n  \"displayNameAttribute\": \"name\",\n  \"mailAttribute\": \"email\",\n  \"groupAttribute\": \"groups\",\n  \"adminGroup\": \"scmadmin\",\n  \"importRealmRoles\": false,\n  \"realmRolesPath\": \"realm_access.roles\",\n  \"migrateLocalUsers\": false,\n  \"forceLogin\": false,\n  \"ssoLogout\": true,\n  \"enabled\": true\n}'</span> http://scm-server.localhost:8080/scm/api/v2/oauth2/configuration</code></pre></div>\n<p><code class=\"language-text\">GET</code> on the same url returns the current configuration. Setting <code class=\"language-text\">enabled: true</code> without a\n<code class=\"language-text\">providerName</code> is rejected with <code class=\"language-text\">400 Bad Request</code>.</p>\n<h2 id=\"migration-of-existing-users\" style=\"position:relative;\"><a href=\"#migration-of-existing-users\" aria-label=\"migration of existing users permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Migration of existing users</h2>\n<p>Users are identified by the username claim, so an instance which already\nauthenticates against another source (e.g. LDAP) can be switched to OAuth2\nwithout losing anything, as long as the identity provider delivers the <strong>same\nuser names</strong>. On the first OAuth2 login the existing account is reused, which\nkeeps everything that is bound to the user name:</p>\n<ul>\n<li>assigned permissions and repository ownership</li>\n<li>group memberships (existing memberships are never removed on the first login,\nbecause the plugin only removes a user from groups which it added itself in a\nprevious login)</li>\n<li>api keys and public keys</li>\n</ul>\n<p>Attributes which the identity provider does <strong>not</strong> deliver keep their stored\nvalue — a missing mail claim does not wipe the mail address of a migrated user.\nThe stored <code class=\"language-text\">active</code> flag is preserved as well, so a deactivated account is not\nreactivated by a login.</p>\n<p>Accounts which can still be used for a <strong>local password login</strong> are not taken\nover by default: the login is rejected with a warning in the log, because\notherwise a user of the identity provider could seize a local account (e.g. the\ninitial <code class=\"language-text\">scmadmin</code>) just by using its name. Users of other external\nauthentications (LDAP, CAS) have no local password and are migrated silently.\nTo migrate local accounts as well, enable <em>Take over local accounts</em>\n(<code class=\"language-text\">migrateLocalUsers</code>) — their local password is removed in that case, because\nthe account is authenticated by the identity provider from then on.</p>\n<p>To check upfront how an existing user is stored:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">curl</span> <span class=\"token parameter variable\">-u</span> <span class=\"token string\">\"scmadmin:secret\"</span> https://scm.example.com/scm/api/v2/users/<span class=\"token operator\">&lt;</span>username<span class=\"token operator\">></span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token parameter variable\">-o</span> <span class=\"token string\">'\"external\":[a-z]*'</span></code></pre></div>\n<h2 id=\"group-and-permission-synchronization\" style=\"position:relative;\"><a href=\"#group-and-permission-synchronization\" aria-label=\"group and permission synchronization permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Group and permission synchronization</h2>\n<p>On <strong>every</strong> login the group claim is compared with the state of the previous login:</p>\n<ul>\n<li>Groups from the claim which do not exist in SCM-Manager yet are <strong>created</strong> (internal groups,\ndescription \"Synchronized by scm-oauth2-plugin\") with the user as first member</li>\n<li>The user is <strong>added</strong> as member to existing groups from the claim</li>\n<li>The user is <strong>removed</strong> from groups which were synchronized on a previous login but are no\nlonger part of the claim</li>\n<li>Groups are <strong>never deleted</strong>, even if they become empty</li>\n<li>Memberships which were assigned manually in other groups are <strong>not</strong> touched — only groups\nthat came from the claim of this user are considered for removal</li>\n<li><em>External</em> groups are skipped, their members are not managed by SCM-Manager</li>\n<li>A failure while synchronizing a group never prevents the login itself</li>\n</ul>\n<p>Characters which SCM-Manager does not allow in a name (<code class=\"language-text\">/ : ? # ; &amp; = % \\</code>, a leading <code class=\"language-text\">@</code> and\nleading/trailing whitespace) are <strong>replaced by an underscore</strong>, so no group is lost. A Keycloak\ngroup mapper with <em>Full group path</em> enabled therefore results in:</p>\n<table>\n<thead>\n<tr>\n<th>Claim</th>\n<th>SCM group</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code class=\"language-text\">/developers</code></td>\n<td><code class=\"language-text\">_developers</code></td>\n</tr>\n<tr>\n<td><code class=\"language-text\">/team/backend</code></td>\n<td><code class=\"language-text\">_team_backend</code></td>\n</tr>\n<tr>\n<td><code class=\"language-text\">SCM RZ-intern</code></td>\n<td><code class=\"language-text\">SCM RZ-intern</code> (unchanged, internal blanks are allowed)</td>\n</tr>\n</tbody>\n</table>\n<p>The sanitized name is used consistently — for the created group, for the membership and for the\nauthorization — so permissions granted to the group really apply. Keep that in mind for the\n<strong>administrator group</strong>: if the identity provider sends <code class=\"language-text\">/scmadmin</code>, the setting has to be\n<code class=\"language-text\">_scmadmin</code>. Two claim values which only differ in invalid characters (<code class=\"language-text\">a/b</code> and <code class=\"language-text\">a:b</code>) end up in\nthe same SCM group.</p>\n<p>Additionally the effective groups of the user are resolved directly from the claim at\nauthorization time (group resolver), so permissions assigned to a group name work even before the\ngroup entity exists.</p>\n<h3 id=\"roles-from-the-access-token\" style=\"position:relative;\"><a href=\"#roles-from-the-access-token\" aria-label=\"roles from the access token permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Roles from the access token</h3>\n<p>Some identity providers deliver roles only in the access token and not in the\nuserinfo response — Keycloak realm roles are the typical example. With\n<em>Import realm roles from access token</em> (<code class=\"language-text\">importRealmRoles</code>) these roles are read\nfrom the token and <strong>added</strong> to the groups of the userinfo response. The\nlocation is configured as a dot separated path (<code class=\"language-text\">realmRolesPath</code>):</p>\n<table>\n<thead>\n<tr>\n<th>Path</th>\n<th>Reads</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code class=\"language-text\">realm_access.roles</code></td>\n<td>Keycloak realm roles (default)</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">resource_access.&lt;client-id>.roles</code></td>\n<td>Keycloak client roles of that client</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">groups</code></td>\n<td>a top level claim of the token</td>\n</tr>\n</tbody>\n</table>\n<p>The path may also contain array indexes (<code class=\"language-text\">realm_access.roles.0</code>). Both arrays\nand single string values are accepted. If the access token is not a JWT (some\nproviders issue opaque tokens) or the path does not exist, no roles are\nimported and the login continues normally.</p>\n<p>The signature of the access token is not verified for this, which is safe\nbecause the token was received directly from the token endpoint of the\nconfigured identity provider over TLS — the same trust level as the userinfo\nresponse.</p>\n<blockquote>\n<p><strong>Note:</strong> every imported role becomes an SCM group (see below). Keycloak realm\nroles often include many technical roles (<code class=\"language-text\">offline_access</code>,\n<code class=\"language-text\">uma_authorization</code>, <code class=\"language-text\">default-roles-&lt;realm></code>, …), so the group list can grow\nconsiderably. If only a few roles are relevant, a client role mapper with\n<code class=\"language-text\">resource_access.&lt;client-id>.roles</code> is the more targeted choice.</p>\n</blockquote>\n<p>If the group claim contains the configured <strong>administrator group</strong> (default <code class=\"language-text\">scmadmin</code>), the user\ngets the <em>global administrator</em> permission (<code class=\"language-text\">*</code>) assigned; if the group is missing, a previously\nassigned permission is revoked on the next login. With a configured admin group the claim is the\nsingle source of truth for this one permission — manually granting it to an OAuth2 user without\nthe group does not survive the next login. Leave the field empty to disable this mechanism.</p>\n<h2 id=\"security-notes\" style=\"position:relative;\"><a href=\"#security-notes\" aria-label=\"security notes permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Security notes</h2>\n<p>The plugin implements the current recommendations for the authorization code flow\n(RFC 6749/9700):</p>\n<ul>\n<li>the <code class=\"language-text\">state</code> is bound to the browser which started the flow via an <code class=\"language-text\">HttpOnly</code>, <code class=\"language-text\">SameSite=Lax</code>\ncookie (<code class=\"language-text\">Secure</code> as soon as the instance runs on HTTPS), so an authorization code of a foreign\nsession cannot be replayed into somebody else's browser</li>\n<li><strong>PKCE</strong> with S256; the verifier never leaves the server. It is omitted only if the discovery\ndocument explicitly advertises no support for it</li>\n<li>the <strong>id token is verified cryptographically</strong>: signature against the JSON Web Key Set of the IdP\n(RS/PS/ES family) or against the client secret (HS family), plus <code class=\"language-text\">iss</code>, <code class=\"language-text\">aud</code>/<code class=\"language-text\">azp</code>, expiry and\nthe <code class=\"language-text\">nonce</code> of this very login. <code class=\"language-text\">alg: none</code> and unknown algorithms are rejected, and the algorithm\ndetermines which kind of key is accepted (no algorithm confusion). An id token which is present\nbut invalid aborts the login</li>\n<li>a fresh <strong>nonce</strong> is sent with every authorization request, so an id token of another session\ncannot be replayed</li>\n<li><strong>roles are only imported from a verified access token</strong>; an opaque token, an invalid signature or\na missing key set means no roles instead of unverified ones</li>\n<li>the client secret is stored encrypted and is write-only in the API</li>\n<li>only absolute <code class=\"language-text\">http</code>/<code class=\"language-text\">https</code> urls are accepted as endpoints</li>\n<li>the forced login only lets requests pass whose access token cookie can actually be validated\n(signature, expiry)</li>\n<li>error details of the identity provider are written to the log only, never into the response</li>\n</ul>\n<p>Two points to keep in mind when operating the plugin:</p>\n<ul>\n<li>\n<p><strong>Whoever controls the group names in the IdP controls the permissions in SCM-Manager</strong> — the\nclaim decides about group memberships and, via the administrator group, about global admin\nrights. Groups/roles should therefore be maintained by IdP administrators only (Keycloak client\nroles are a good fit).</p>\n</li>\n<li>\n<p><strong>The permission <code class=\"language-text\">configuration:write:oauth2</code> has to be treated like global admin</strong>: whoever may\nchange the configuration can point the endpoints at a different host and thereby both receive the\nclient secret and take over the authentication completely.</p>\n</li>\n<li>\n<p><strong>A key source is required for the token verification.</strong> With a discovery url it is taken from the\ndocument (<code class=\"language-text\">jwks_uri</code>) automatically. If the endpoints are configured manually, configure <code class=\"language-text\">jwksUrl</code>\nas well — otherwise the plugin logs a warning, discards the id token (the SSO logout then works\nwithout <code class=\"language-text\">id_token_hint</code>) and imports no roles from the access token. Note the difference: a missing\nkey set url only costs the id token, whereas a configured key set which cannot be fetched or does\nnot contain the key of the token makes the login fail — an id token that is present has to be\nverifiable (fail closed). The key set is cached for one hour and re-fetched when a token names an\nunknown key, so a key rotation at the IdP needs no action.</p>\n</li>\n</ul>\n<p>Run the instance behind TLS — the client secret is transmitted in the body when the configuration\nis saved, and the session cookie is only marked <code class=\"language-text\">Secure</code> on an HTTPS connection.</p>\n<h2 id=\"keycloak-example\" style=\"position:relative;\"><a href=\"#keycloak-example\" aria-label=\"keycloak example permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Keycloak example</h2>\n<p><a href=\"scm-server.json\"><code class=\"language-text\">scm-server.json</code></a> contains an export of a working sample client for the setup\ndescribed above (SCM-Manager reachable at <code class=\"language-text\">http://scm-server.localhost:8080/scm</code>). Import it in\nthe Keycloak admin console via <em>Clients → Import client</em> and generate a client secret afterwards —\nthe file contains the placeholder <code class=\"language-text\">GENERATE_YOUR_OWN_SECRET_IN_KEYCLOAK</code> instead of a real secret.</p>\n<p>The important settings of the sample client:</p>\n<ul>\n<li><strong>Confidential client</strong>: <code class=\"language-text\">publicClient: false</code>, <code class=\"language-text\">clientAuthenticatorType: client-secret</code>,\nonly the standard flow (authorization code) is enabled — no implicit flow, no direct access\ngrants, no service account</li>\n<li><strong>Redirect uri</strong>: <code class=\"language-text\">/scm/api/v2/oauth2/auth/callback</code> (relative to the root url\n<code class=\"language-text\">http://scm-server.localhost:8080</code>)</li>\n<li><strong>Post logout redirect uri</strong> (<code class=\"language-text\">attributes.\"post.logout.redirect.uris\"</code>):\n<code class=\"language-text\">http://scm-server.localhost:8080/scm</code> — required for the full SSO logout (adjust it to your\nbase url; avoid wildcards in production)</li>\n<li><strong>Logout confirmation disabled</strong> (<code class=\"language-text\">attributes.\"logout.confirmation.enabled\": \"false\"</code>), so the\nRP-initiated logout runs without user interaction</li>\n<li><strong>Groups claim via client roles</strong>: the protocol mapper <code class=\"language-text\">client roles</code>\n(<code class=\"language-text\">oidc-usermodel-client-role-mapper</code>) maps the <strong>client roles of <code class=\"language-text\">scm-server</code></strong> to the claim\n<code class=\"language-text\">groups</code> — with <code class=\"language-text\">\"userinfo.token.claim\": \"true\"</code>, which is essential because the plugin reads\nthe claims from the userinfo endpoint</li>\n<li><strong>Scopes</strong>: <code class=\"language-text\">profile</code> is a default scope, <code class=\"language-text\">email</code> an optional one — the plugin requests\n<code class=\"language-text\">openid profile email</code>, so the email claim is delivered if the email scope is granted</li>\n</ul>\n<p>With this mapper setup, SCM groups are managed as <strong>Keycloak client roles</strong> of the <code class=\"language-text\">scm-server</code>\nclient:</p>\n<ol>\n<li>Keycloak → Clients → <code class=\"language-text\">scm-server</code> → <em>Roles</em> → create role, e.g. <code class=\"language-text\">scmadmin</code> or <code class=\"language-text\">developers</code></li>\n<li>Assign the role to users (Users → <em>Role mapping</em> → Assign role → filter by client)</li>\n<li>On the next SCM login the role shows up in the <code class=\"language-text\">groups</code> claim, the corresponding SCM group is\ncreated/updated and — in case of <code class=\"language-text\">scmadmin</code> — the global administrator permission is assigned</li>\n</ol>\n<p>Alternatively a Keycloak <em>Group Membership</em> mapper (claim <code class=\"language-text\">groups</code>, <em>Add to userinfo</em> enabled,\n<em>Full group path</em> disabled) can be used to map real Keycloak groups instead of client roles.</p>\n<h2 id=\"build-and-testing\" style=\"position:relative;\"><a href=\"#build-and-testing\" aria-label=\"build and testing permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Build and testing</h2>\n<p>The plugin can be compiled and packaged with the following tasks:</p>\n<ul>\n<li>clean - <code class=\"language-text\">gradle clean</code> - deletes the build directory</li>\n<li>run - <code class=\"language-text\">gradle run</code> - starts an SCM-Manager with the plugin pre-installed and with livereload for the ui</li>\n<li>build - <code class=\"language-text\">gradle build</code> - executes all checks, tests and builds the smp inclusive javadoc and source jar</li>\n<li>test - <code class=\"language-text\">gradle test</code> - run all java tests</li>\n<li>ui-test - <code class=\"language-text\">gradle ui-test</code> - run all ui tests</li>\n<li>check - <code class=\"language-text\">gradle check</code> - executes all registered checks and tests (java and ui)</li>\n<li>fix - <code class=\"language-text\">gradle fix</code> - fixes all fixable findings of the check task</li>\n<li>smp - <code class=\"language-text\">gradle smp</code> - Builds the smp file, without the execution of checks and tests</li>\n<li>javadoc - <code class=\"language-text\">gradle javadoc</code> - generates the api documentation below <code class=\"language-text\">build/docs/javadoc</code></li>\n</ul>\n<p>Gradle has no profiles like maven; the build evaluates the project property <code class=\"language-text\">stage</code> instead.\nWith <code class=\"language-text\">-Pstage=release</code> the api documentation is written to <a href=\"docs/javadoc\">docs/javadoc</a> next to\nthe sources instead of into the build directory, so it is committed together with the release:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># regenerate only the documentation</span>\n./gradlew javadoc <span class=\"token parameter variable\">-Pstage</span><span class=\"token operator\">=</span>release\n\n<span class=\"token comment\"># complete release build including the committed documentation</span>\n./gradlew build <span class=\"token parameter variable\">-Pstage</span><span class=\"token operator\">=</span>release</code></pre></div>\n<p>The directory is emptied before it is regenerated, so pages of removed classes do not stay behind.\nWithout the property everything stays in the build directory and <code class=\"language-text\">docs/javadoc</code> is untouched.</p>\n<p>For the development and testing the <code class=\"language-text\">run</code> task of the plugin can be used:</p>\n<ul>\n<li>run - <code class=\"language-text\">gradle run</code> - starts scm-manager with the plugin pre-installed.</li>\n</ul>\n<p>If the plugin was started with <code class=\"language-text\">gradle run</code>, the default browser of the os should be automatically opened.\nIf the browser does not start automatically, start it manually and go to <a href=\"http://localhost:8081/scm\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http://localhost:8081/scm</a>.</p>\n<p>In this mode each change to web files (src/main/js or src/main/webapp), should trigger reload of the browser with the made changes.</p>\n<h2 id=\"running-with-an-oidc-provider-using-docker\" style=\"position:relative;\"><a href=\"#running-with-an-oidc-provider-using-docker\" aria-label=\"running with an oidc provider using docker permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Running with an OIDC provider using Docker</h2>\n<p>If you don't have an OIDC provider at hand, you can just run one using Docker. To do so, you can use the\nprovided <code class=\"language-text\">docker-compose.yml</code> file and the <code class=\"language-text\">dex.yaml</code> configuration. Assuming a working Docker installation,\nsimply run</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\"><span class=\"token function\">docker</span> compose up</code></pre></div>\n<p>This will start a small Dex provider with two users: <code class=\"language-text\">trillian@hog.org</code> and <code class=\"language-text\">dent@example.com</code>, both with the password <code class=\"language-text\">secret</code>.\nYou can simply add mor user accounts by editing the <code class=\"language-text\">dex.yaml</code> file. You can generate hashes for other passwords with this command:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\"><span class=\"token function\">docker</span> run <span class=\"token parameter variable\">--rm</span> httpd:alpine htpasswd <span class=\"token parameter variable\">-C</span> <span class=\"token number\">10</span> <span class=\"token parameter variable\">-bnB</span> <span class=\"token string\">\"\"</span> <span class=\"token string\">\"other-password\"</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> <span class=\"token parameter variable\">-d</span> <span class=\"token string\">':\\n'</span></code></pre></div>\n<p>To configure SCM-Manager, use this cURL call:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\"><span class=\"token function\">curl</span> <span class=\"token parameter variable\">-vu</span> scmadmin:scmadmin <span class=\"token punctuation\">\\</span>\n  http://localhost:8081/scm/api/v2/oauth2/configuration <span class=\"token punctuation\">\\</span>\n  <span class=\"token parameter variable\">-H</span> <span class=\"token string\">'Content-Type: application/vnd.scmm-oauth2config+json;v=2'</span> <span class=\"token punctuation\">\\</span>\n  <span class=\"token parameter variable\">-XPUT</span> <span class=\"token punctuation\">\\</span>\n <span class=\"token parameter variable\">--data</span> <span class=\"token string\">'{\n   \"providerName\":\"Local Test\",\n   \"discoveryUrl\":\"http://localhost:5556/dex/.well-known/openid-configuration\",\n   \"clientId\":\"scm-manager\",\n   \"clientSecret\":\"scm-secret-12345\",\n   \"clientSecretSet\":false,\n   \"scopes\":\"openid profile email\",\n   \"usernameAttribute\":\"name\",\n   \"displayNameAttribute\":\"name\",\n   \"mailAttribute\":\"email\",\n   \"groupAttribute\":\"groups\",\n   \"adminGroup\":\"\",\n   \"importRealmRoles\":false,\n   \"realmRolesPath\":\"realm_access.roles\",\n   \"forceLogin\":false,\n   \"ssoLogout\":false,\n   \"migrateLocalUsers\":false,\n   \"enabled\":true\n }'</span></code></pre></div>\n<h2 id=\"directory--file-structure\" style=\"position:relative;\"><a href=\"#directory--file-structure\" aria-label=\"directory  file structure permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Directory &#x26; File structure</h2>\n<p>A quick look at the files and directories you'll see in an SCM-Manager project.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">.\n├── node_modules/\n├── src/\n|   ├── main/\n|   |   ├── java/\n|   |   ├── js/\n|   |   └── resources/\n|   └── test/\n|       ├── java/\n|       └── resources/\n├── .editorconfig\n├── .gitignore\n├── build.gradle\n├── CHANGELOG.md\n├── gradle.properties\n├── gradlew\n├── LICENSE.txt\n├── package.json\n├── README.md\n├── scm-server.json\n├── settings.gradle\n├── tsconfig.json\n└── yarn.lock</code></pre></div>\n<ol>\n<li>\n<p><strong><code class=\"language-text\">node_modules/</code></strong>: This directory contains all modules of code that your project depends on (npm packages) are automatically installed.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">src/</code></strong>: This directory will contain all code related to what you see or not. <code class=\"language-text\">src</code> is a convention for “source code”.</p>\n<ol>\n<li><strong><code class=\"language-text\">main/</code></strong>\n<ol>\n<li><strong><code class=\"language-text\">java/</code></strong>: This directory contains the Java code.</li>\n<li><strong><code class=\"language-text\">js/</code></strong>: This directory contains the JavaScript code for the web ui, inclusive unit tests: suffixed with <code class=\"language-text\">.test.ts</code></li>\n<li><strong><code class=\"language-text\">resources/</code></strong>: This directory contains the classpath resources.</li>\n</ol>\n</li>\n<li><strong><code class=\"language-text\">test/</code></strong>\n<ol>\n<li><strong><code class=\"language-text\">java/</code></strong>: This directory contains the Java unit tests.</li>\n<li><strong><code class=\"language-text\">resources/</code></strong>: This directory contains classpath resources for unit tests.</li>\n</ol>\n</li>\n</ol>\n</li>\n<li>\n<p><strong><code class=\"language-text\">.editorconfig</code></strong>: This is a configuration file for your editor using <a href=\"https://editorconfig.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">EditorConfig</a>. The file specifies a style that IDEs use for code.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">.gitignore</code></strong>: This file tells git which files it should not track / not maintain a version history for.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">build.gradle</code></strong>: Gradle build configuration, which also includes things like metadata.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">CHANGELOG.md</code></strong>: All notable changes to this project will be documented in this file.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">gradle.properties</code></strong>: Defines the module version.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">gradlew</code></strong>: Bundled gradle wrapper if you don't have gradle installed.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">LICENSE.txt</code></strong>: This project is licensed under AGPLv3.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">package.json</code></strong>: Here you can find the dependency/build configuration and dependencies for the frontend.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">README.md</code></strong>: This file, containing useful reference information about the project. A German version is available in <code class=\"language-text\">README_de.md</code>.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">scm-server.json</code></strong>: Export of the Keycloak sample client (see <a href=\"#keycloak-example\">Keycloak example</a>).</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">settings.gradle</code></strong>: Gradle settings configuration.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">tsconfig.json</code></strong> This is the typescript configuration file.</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">yarn.lock</code></strong>: This is the ui dependency configuration.</p>\n</li>\n</ol>\n<h2 id=\"further-documentation\" style=\"position:relative;\"><a href=\"#further-documentation\" aria-label=\"further documentation permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Further documentation</h2>\n<p>Below <code class=\"language-text\">docs/</code> you will find the generated api documentation as well as four documents (in German)\nwhich describe the plugin from a data protection and information security point of view:</p>\n<table>\n<thead>\n<tr>\n<th>Document</th>\n<th>Content</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"docs/PROZESS_BESCHREIBUNG.md\">PROZESS_BESCHREIBUNG.md</a></td>\n<td>Processing operations, data categories, storage locations, retention and recipients — basis for a record of processing activities (art. 30 GDPR)</td>\n</tr>\n<tr>\n<td><a href=\"docs/COMPLIANCE_BERICHT.md\">COMPLIANCE_BERICHT.md</a></td>\n<td>Assessment against GDPR and the NIS2 risk management measures (art. 21(2)), including implemented measures, findings and residual risks</td>\n</tr>\n<tr>\n<td><a href=\"docs/DATENSCHUTZ_FOLGEABSCHAETZUNG.md\">DATENSCHUTZ_FOLGEABSCHAETZUNG.md</a></td>\n<td>Threshold analysis and data protection impact assessment (art. 35 GDPR)</td>\n</tr>\n<tr>\n<td><a href=\"docs/SOURCECODE_STATISTIK.md\">SOURCECODE_STATISTIK.md</a></td>\n<td>Size, test coverage, documentation ratio and dependencies</td>\n</tr>\n<tr>\n<td><a href=\"docs/javadoc\">docs/javadoc</a></td>\n<td>Api documentation, regenerated with <code class=\"language-text\">./gradlew javadoc -Pstage=release</code></td>\n</tr>\n</tbody>\n</table>\n<p>The documents describe the software; the organisational parts (legal basis, retention periods,\nresponsibilities) have to be completed by the operator of the instance.</p>\n<h2 id=\"need-help\" style=\"position:relative;\"><a href=\"#need-help\" aria-label=\"need help permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Need help?</h2>\n<p>Looking for more guidance? Full documentation lives on the SCM-Manager <a href=\"https://scm-manager.org/docs/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">homepage</a> or the dedicated pages for the <a href=\"https://scm-manager.org/plugins/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">plugins</a>.</p>"}},"pageContext":{"name":"scm-oauth2-plugin"}},
    "staticQueryHashes": ["1973669194","2742214048","2956773653"]}