diff --git a/CHANGES.md b/CHANGES.md
index aa9161905..5033c1dfa 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,4 +1,5 @@
- Start using docker compose V2 (from `docker-compose` to `docker compose`)
+- Added support for selfhosted Gitlab (Oauth)
# Version 0.14.2
diff --git a/app/config/providers.php b/app/config/providers.php
index 879557982..5a591e325 100644
--- a/app/config/providers.php
+++ b/app/config/providers.php
@@ -117,7 +117,7 @@ return [ // Ordered by ABC.
'icon' => 'icon-gitlab',
'enabled' => true,
'sandbox' => false,
- 'form' => false,
+ 'form' => 'gitlab.phtml',
'beta' => false,
'mock' => false,
],
diff --git a/app/views/console/users/oauth/gitlab.phtml b/app/views/console/users/oauth/gitlab.phtml
new file mode 100644
index 000000000..ad6b11e13
--- /dev/null
+++ b/app/views/console/users/oauth/gitlab.phtml
@@ -0,0 +1,12 @@
+getParam('provider', '');
+?>
+
+
+
+
+
+
+
+
+
diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js
index 5fde27c63..4239d33b5 100644
--- a/public/dist/scripts/app-all.js
+++ b/public/dist/scripts/app-all.js
@@ -3896,7 +3896,7 @@ list["filters-"+filter.key]=params[key][i];}}}}
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
-console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}}
+console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")}
let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}}
function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}}
diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js
index f5545c908..6eba62e98 100644
--- a/public/dist/scripts/app.js
+++ b/public/dist/scripts/app.js
@@ -796,7 +796,7 @@ list["filters-"+filter.key]=params[key][i];}}}}
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
-console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}}
+console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")}
let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}}
function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}}
diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js
index ca2d3b275..66b81cba5 100644
--- a/public/scripts/views/forms/oauth-custom.js
+++ b/public/scripts/views/forms/oauth-custom.js
@@ -25,7 +25,11 @@
"Auth0": {
"clientSecret": "oauth2Auth0ClientSecret",
"auth0Domain": "oauth2Auth0Domain"
- }
+ },
+ "Gitlab": {
+ "endpoint": "oauth2GitlabEndpoint",
+ "clientSecret": "oauth2GitlabClientSecret",
+ },
}
let provider = element.getAttribute("data-forms-oauth-custom");
if (!provider || !providers.hasOwnProperty(provider)) { console.error("Provider for custom form not set or unknown") }
diff --git a/src/Appwrite/Auth/OAuth2/Gitlab.php b/src/Appwrite/Auth/OAuth2/Gitlab.php
index ab230c776..7d98bf192 100644
--- a/src/Appwrite/Auth/OAuth2/Gitlab.php
+++ b/src/Appwrite/Auth/OAuth2/Gitlab.php
@@ -39,7 +39,7 @@ class Gitlab extends OAuth2
*/
public function getLoginURL(): string
{
- return 'https://gitlab.com/oauth/authorize?' . \http_build_query([
+ return $this->getEndpoint() . '/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -58,10 +58,10 @@ class Gitlab extends OAuth2
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
- 'https://gitlab.com/oauth/token?' . \http_build_query([
+ $this->getEndpoint() . '/oauth/token?' . \http_build_query([
'code' => $code,
'client_id' => $this->appID,
- 'client_secret' => $this->appSecret,
+ 'client_secret' => $this->getAppSecret()['clientSecret'],
'redirect_uri' => $this->callback,
'grant_type' => 'authorization_code'
])
@@ -80,10 +80,10 @@ class Gitlab extends OAuth2
{
$this->tokens = \json_decode($this->request(
'POST',
- 'https://gitlab.com/oauth/token?' . \http_build_query([
+ $this->getEndpoint() . '/oauth/token?' . \http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
- 'client_secret' => $this->appSecret,
+ 'client_secret' => $this->getAppSecret()['clientSecret'],
'grant_type' => 'refresh_token'
])
), true);
@@ -163,10 +163,39 @@ class Gitlab extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
- $user = $this->request('GET', 'https://gitlab.com/api/v4/user?access_token=' . \urlencode($accessToken));
+ $user = $this->request('GET', $this->getEndpoint() . '/api/v4/user?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;
}
+
+ /**
+ * Decode the JSON stored in appSecret
+ *
+ * @return array
+ */
+ protected function getAppSecret(): array
+ {
+ try {
+ $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
+ } catch (\Throwable $th) {
+ throw new \Exception('Invalid secret');
+ }
+ return $secret;
+ }
+
+
+ /**
+ * Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
+ *
+ * @return string
+ */
+ protected function getEndpoint(): string
+ {
+ $defaultEndpoint = 'https://gitlab.com';
+ $secret = $this->getAppSecret();
+ $endpoint = $secret['endpoint'] ?? $defaultEndpoint;
+ return empty($endpoint) ? $defaultEndpoint : $endpoint;
+ }
}