feat: ✨ Makes Microsoft OAuth Provider configureable
This commit is contained in:
parent
fb2ec0355a
commit
9922a6d6cf
10 changed files with 1083 additions and 901 deletions
|
@ -127,7 +127,7 @@ return [ // Ordered by ABC.
|
||||||
'icon' => 'icon-windows',
|
'icon' => 'icon-windows',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'sandbox' => false,
|
'sandbox' => false,
|
||||||
'form' => false,
|
'form' => 'microsoft.phtml',
|
||||||
'beta' => false,
|
'beta' => false,
|
||||||
'mock' => false,
|
'mock' => false,
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
use Appwrite\Utopia\View;
|
||||||
|
|
||||||
$providers = $this->getParam('providers', []);
|
$providers = $this->getParam('providers', []);
|
||||||
$auth = $this->getParam('auth', []);
|
$auth = $this->getParam('auth', []);
|
||||||
$smtpEnabled = $this->getParam('smtpEnabled', false);
|
$smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||||
|
@ -475,9 +477,12 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||||
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
|
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
|
||||||
<input name="secret" data-forms-show-secret id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="password" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
|
<input name="secret" data-forms-show-secret id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="password" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Bundle ID <span class="tooltip" data-tooltip="Attribute internal display name"><i class="icon-info-circled"></i></span></label>
|
<?php
|
||||||
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="com.company.appname" />
|
$form_view = new View(__DIR__.'/oauth/'.$this->escape($form));
|
||||||
<input name="secret" data-forms-oauth-apple id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
|
echo $form_view
|
||||||
|
->setParam("provider",$provider)
|
||||||
|
->render();
|
||||||
|
?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="info row thin margin-bottom margin-top">
|
<div class="info row thin margin-bottom margin-top">
|
||||||
|
|
7
app/views/console/users/oauth/apple.phtml
Normal file
7
app/views/console/users/oauth/apple.phtml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
$provider = $this->getParam('provider', '');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Bundle ID <span class="tooltip" data-tooltip="Attribute internal display name"><i class="icon-info-circled"></i></span></label>
|
||||||
|
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="com.company.appname" />
|
||||||
|
<input name="secret" data-forms-oauth-apple id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
|
12
app/views/console/users/oauth/microsoft.phtml
Normal file
12
app/views/console/users/oauth/microsoft.phtml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
$provider = $this->getParam('provider', '');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Application (Client) ID<span class="tooltip" data-tooltip="Provided by AzureAD"><i class="icon-info-circled"></i></span></label>
|
||||||
|
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Application ID" />
|
||||||
|
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Created by you in AzureAD Portal"><i class="icon-info-circled"></i></span></label>
|
||||||
|
<input name="appSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
|
||||||
|
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>TenantId">Target Tenant<span class="tooltip" data-tooltip="'common', 'organizations', 'consumers' or your TenantId"><i class="icon-info-circled"></i></span><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints">More info</a></label>
|
||||||
|
<input name="appSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>TenantId" type="text" autocomplete="off" placeholder="'common', 'organizations', 'consumers' or your TenantId" />
|
||||||
|
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
|
||||||
|
<input name="secret" data-forms-oauth-microsoft id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
|
|
@ -58,6 +58,7 @@ const configApp = {
|
||||||
'public/scripts/views/forms/move-up.js',
|
'public/scripts/views/forms/move-up.js',
|
||||||
'public/scripts/views/forms/nav.js',
|
'public/scripts/views/forms/nav.js',
|
||||||
'public/scripts/views/forms/oauth-apple.js',
|
'public/scripts/views/forms/oauth-apple.js',
|
||||||
|
'public/scripts/views/forms/oauth-microsoft.js',
|
||||||
'public/scripts/views/forms/password-meter.js',
|
'public/scripts/views/forms/password-meter.js',
|
||||||
'public/scripts/views/forms/pell.js',
|
'public/scripts/views/forms/pell.js',
|
||||||
'public/scripts/views/forms/required.js',
|
'public/scripts/views/forms/required.js',
|
||||||
|
|
930
public/dist/scripts/app-all.js
vendored
930
public/dist/scripts/app-all.js
vendored
File diff suppressed because it is too large
Load diff
926
public/dist/scripts/app-dep.js
vendored
926
public/dist/scripts/app-dep.js
vendored
File diff suppressed because it is too large
Load diff
4
public/dist/scripts/app.js
vendored
4
public/dist/scripts/app.js
vendored
|
@ -782,6 +782,10 @@ console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i
|
||||||
function sync(){if(!element.value){return;}
|
function sync(){if(!element.value){return;}
|
||||||
let json={};try{json=JSON.parse(element.value);}catch(error){console.error('Failed to parse secret key');}
|
let json={};try{json=JSON.parse(element.value);}catch(error){console.error('Failed to parse secret key');}
|
||||||
teamID.value=json.teamID||'';keyID.value=json.keyID||'';p8.value=json.p8||'';}
|
teamID.value=json.teamID||'';keyID.value=json.keyID||'';p8.value=json.p8||'';}
|
||||||
|
sync();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-microsoft",controller:function(element){let clientSecret=document.getElementById("oauth2MicrosoftClientSecret");let tenantId=document.getElementById("oauth2MicrosoftTenantId");element.addEventListener('change',sync);clientSecret.addEventListener('change',update);tenantId.addEventListener('change',update);function update(){let json={};json.clientSecret=clientSecret.value;json.tenantId=tenantId.value;element.value=JSON.stringify(json);}
|
||||||
|
function sync(){if(!element.value){return;}
|
||||||
|
let json={};try{json=JSON.parse(element.value);}catch(error){console.error('Failed to parse secret key');}
|
||||||
|
clientSecret.value=json.clientSecret||'';tenantId.value=json.tenantId||'';}
|
||||||
sync();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
|
sync();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i<password.length;i++){letters[password[i]]=(letters[password[i]]||0)+1;score+=5.0/letters[password[i]];}
|
||||||
var variations={digits:/\d/.test(password),lower:/[a-z]/.test(password),upper:/[A-Z]/.test(password),nonWords:/\W/.test(password)};var variationCount=0;for(var check in variations){if(variations.hasOwnProperty(check)){variationCount+=variations[check]===true?1:0;}}
|
var variations={digits:/\d/.test(password),lower:/[a-z]/.test(password),upper:/[A-Z]/.test(password),nonWords:/\W/.test(password)};var variationCount=0;for(var check in variations){if(variations.hasOwnProperty(check)){variationCount+=variations[check]===true?1:0;}}
|
||||||
score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){var score=calc(this.value);if(""===this.value)return(meter.className="password-meter");if(score>60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:'<i class="icon-bold"></i>'},{name:"underline",icon:'<i class="icon-underline"></i>'},{name:"italic",icon:'<i class="icon-italic"></i>'},{name:"olist",icon:'<i class="icon-list-numbered"></i>'},{name:"ulist",icon:'<i class="icon-list-bullet"></i>'},{name:"link",icon:'<i class="icon-link"></i>'}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;}
|
score+=(variationCount-1)*10;return parseInt(score);};var callback=function(){var score=calc(this.value);if(""===this.value)return(meter.className="password-meter");if(score>60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:'<i class="icon-bold"></i>'},{name:"underline",icon:'<i class="icon-underline"></i>'},{name:"italic",icon:'<i class="icon-italic"></i>'},{name:"olist",icon:'<i class="icon-list-numbered"></i>'},{name:"ulist",icon:'<i class="icon-list-bullet"></i>'},{name:"link",icon:'<i class="icon-link"></i>'}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;}
|
||||||
|
|
50
public/scripts/views/forms/oauth-microsoft.js
Normal file
50
public/scripts/views/forms/oauth-microsoft.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
(function (window) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//TODO: Make this generic
|
||||||
|
|
||||||
|
window.ls.container.get("view").add({
|
||||||
|
selector: "data-forms-oauth-microsoft",
|
||||||
|
controller: function (element) {
|
||||||
|
// element contains the final secret
|
||||||
|
|
||||||
|
// Get all custom input fields by their ID
|
||||||
|
let clientSecret = document.getElementById("oauth2MicrosoftClientSecret");
|
||||||
|
let tenantId = document.getElementById("oauth2MicrosoftTenantId");
|
||||||
|
|
||||||
|
// Add Change Listeners for element and all custom input fields
|
||||||
|
|
||||||
|
element.addEventListener('change', sync);
|
||||||
|
clientSecret.addEventListener('change', update);
|
||||||
|
tenantId.addEventListener('change', update);
|
||||||
|
|
||||||
|
// Build the JSON based on input in custom input fields
|
||||||
|
function update() {
|
||||||
|
let json = {};
|
||||||
|
|
||||||
|
json.clientSecret = clientSecret.value;
|
||||||
|
json.tenantId = tenantId.value;
|
||||||
|
|
||||||
|
element.value = JSON.stringify(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync() {
|
||||||
|
if (!element.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(element.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse secret key');
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSecret.value = json.clientSecret || '';
|
||||||
|
tenantId.value = json.tenantId || '';
|
||||||
|
}
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(window);
|
|
@ -36,7 +36,7 @@ class Microsoft extends OAuth2
|
||||||
*/
|
*/
|
||||||
public function getLoginURL(): string
|
public function getLoginURL(): string
|
||||||
{
|
{
|
||||||
return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?'.\http_build_query([
|
return 'https://login.microsoftonline.com/'.$this->getTenantId().'/oauth2/v2.0/authorize?'.\http_build_query([
|
||||||
'client_id' => $this->appID,
|
'client_id' => $this->appID,
|
||||||
'redirect_uri' => $this->callback,
|
'redirect_uri' => $this->callback,
|
||||||
'state'=> \json_encode($this->state),
|
'state'=> \json_encode($this->state),
|
||||||
|
@ -57,12 +57,12 @@ class Microsoft extends OAuth2
|
||||||
|
|
||||||
$accessToken = $this->request(
|
$accessToken = $this->request(
|
||||||
'POST',
|
'POST',
|
||||||
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
'https://login.microsoftonline.com/'.$this->getTenantId().'/oauth2/v2.0/token',
|
||||||
$headers,
|
$headers,
|
||||||
\http_build_query([
|
\http_build_query([
|
||||||
'code' => $code,
|
'code' => $code,
|
||||||
'client_id' => $this->appID,
|
'client_id' => $this->appID,
|
||||||
'client_secret' => $this->appSecret,
|
'client_secret' => $this->getClientSecret(),
|
||||||
'redirect_uri' => $this->callback,
|
'redirect_uri' => $this->callback,
|
||||||
'scope' => \implode(' ', $this->getScopes()),
|
'scope' => \implode(' ', $this->getScopes()),
|
||||||
'grant_type' => 'authorization_code'
|
'grant_type' => 'authorization_code'
|
||||||
|
@ -141,4 +141,39 @@ class Microsoft extends OAuth2
|
||||||
|
|
||||||
return $this->user;
|
return $this->user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the Client Secret from the JSON stored in appSecret
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getClientSecret():string
|
||||||
|
{
|
||||||
|
$secret = $this->decodeJson();
|
||||||
|
|
||||||
|
return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the JSON stored in appSecret
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function decodeJson():array{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$secret = \json_decode($this->appSecret, true);
|
||||||
|
} 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 getTenantId():string
|
||||||
|
{
|
||||||
|
$secret = $this->decodeJson();
|
||||||
|
return (isset($secret['tenantId'])) ? $secret['tenantId'] : 'common';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue