ソースを参照

Merge pull request #4588 from Viehlieb/feature/direct_login_from_oidc

Feature/direct login from OIDC
Lauri Ojansivu 3 年 前
コミット
24598eaf00

+ 5 - 5
CHANGELOG.md

@@ -935,7 +935,7 @@ This release removes the following new features:
 
 
 - [Revert change from WeKan v5.81: At Sandstorm, every WeKan user is now WeKan Admin and has Admin Panel](https://github.com/wekan/wekan/commit/ebc7741fcb9ad854234921ed0546255411adeec9).
 - [Revert change from WeKan v5.81: At Sandstorm, every WeKan user is now WeKan Admin and has Admin Panel](https://github.com/wekan/wekan/commit/ebc7741fcb9ad854234921ed0546255411adeec9).
   Thanks to ocdtrekkie and xet7.
   Thanks to ocdtrekkie and xet7.
-    
+
 and adds the following new features:
 and adds the following new features:
 
 
 - [List header contains now a button to add the card to the bottom of the list](https://github.com/wekan/wekan/pull/4195).
 - [List header contains now a button to add the card to the bottom of the list](https://github.com/wekan/wekan/pull/4195).
@@ -1187,7 +1187,7 @@ and fixes the following bugs:
   Thanks to niklasdahlheimer.
   Thanks to niklasdahlheimer.
 - [Popup fixes: Archive cards, upload attachements etc](https://github.com/wekan/wekan/pull/4101).
 - [Popup fixes: Archive cards, upload attachements etc](https://github.com/wekan/wekan/pull/4101).
   Thanks to mfilser.
   Thanks to mfilser.
-  
+
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
 
 
 # v5.68 2021-10-27 WeKan ® release
 # v5.68 2021-10-27 WeKan ® release
@@ -2158,7 +2158,7 @@ This release adds the following new features:
 
 
 - [Custom Field "String Template"](https://github.com/wekan/wekan/pull/3701).
 - [Custom Field "String Template"](https://github.com/wekan/wekan/pull/3701).
   Thanks to tod31.
   Thanks to tod31.
-- [1) Admin reports. An option added to the admin panel that has reports an admin can run. 
+- [1) Admin reports. An option added to the admin panel that has reports an admin can run.
       Right now it has two reports for attachments and broken cards.
       Right now it has two reports for attachments and broken cards.
   2) Add the creator avatar to `cardDetails` and `minicard`.  Avatar is only shown if it is selected in card settings.
   2) Add the creator avatar to `cardDetails` and `minicard`.  Avatar is only shown if it is selected in card settings.
   3) Added a new search operator `creator`.
   3) Added a new search operator `creator`.
@@ -2891,7 +2891,7 @@ and fixes the following bugs:
   Thanks to xet7.
   Thanks to xet7.
 - [Rules not copied during board copy](https://github.com/wekan/wekan/pull/3458).
 - [Rules not copied during board copy](https://github.com/wekan/wekan/pull/3458).
   Thanks to jrsupplee.
   Thanks to jrsupplee.
- 
+
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
 
 
 # v4.83 2021-01-20 Wekan release
 # v4.83 2021-01-20 Wekan release
@@ -2912,7 +2912,7 @@ and fixes the following bugs:
   Thanks to jrsupplee.
   Thanks to jrsupplee.
 
 
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
-  
+
 # v4.82 2021-01-20 Wekan release
 # v4.82 2021-01-20 Wekan release
 
 
 This release adds the following new features:
 This release adds the following new features:

+ 37 - 26
client/components/main/layouts.js

@@ -4,7 +4,7 @@ BlazeLayout.setRoot('body');
 
 
 let alreadyCheck = 1;
 let alreadyCheck = 1;
 let isCheckDone = false;
 let isCheckDone = false;
-
+let counter = 0;
 const validator = {
 const validator = {
   set(obj, prop, value) {
   set(obj, prop, value) {
     if (prop === 'state' && value !== 'signIn') {
     if (prop === 'state' && value !== 'signIn') {
@@ -21,7 +21,7 @@ const validator = {
 
 
 // let isSettingDatabaseFctCallDone = false;
 // let isSettingDatabaseFctCallDone = false;
 
 
-Template.userFormsLayout.onCreated(function() {
+Template.userFormsLayout.onCreated(function () {
   const templateInstance = this;
   const templateInstance = this;
   templateInstance.currentSetting = new ReactiveVar();
   templateInstance.currentSetting = new ReactiveVar();
   templateInstance.isLoading = new ReactiveVar(false);
   templateInstance.isLoading = new ReactiveVar(false);
@@ -37,7 +37,7 @@ Template.userFormsLayout.onCreated(function() {
       }
       }
 
 
       // isSettingDatabaseFctCallDone = true;
       // isSettingDatabaseFctCallDone = true;
-      if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
+      if (currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
         document.getElementById("isSettingDatabaseCallDone").style.display = 'none';
         document.getElementById("isSettingDatabaseCallDone").style.display = 'none';
       else
       else
         document.getElementById("isSettingDatabaseCallDone").style.display = 'block';
         document.getElementById("isSettingDatabaseCallDone").style.display = 'block';
@@ -50,6 +50,18 @@ Template.userFormsLayout.onCreated(function() {
     }
     }
   });
   });
 
 
+  if (!Meteor.user()?.profile) {
+      Meteor.call('isOidcRedirectionEnabled', (_, result) => {
+        if (result) {
+          AccountsTemplates.options.socialLoginStyle = 'redirect';
+          options = {
+            loginStyle: AccountsTemplates.options.socialLoginStyle,
+          };
+          Meteor.loginWithOidc(options);
+        }
+        else console.log("oidc redirect not set");
+      });
+  }
   Meteor.call('isDisableRegistration', (_, result) => {
   Meteor.call('isDisableRegistration', (_, result) => {
     if (result) {
     if (result) {
       $('.at-signup-link').hide();
       $('.at-signup-link').hide();
@@ -81,22 +93,22 @@ Template.userFormsLayout.helpers({
   //   return isSettingDatabaseFctCallDone;
   //   return isSettingDatabaseFctCallDone;
   // },
   // },
 
 
-  isLegalNoticeLinkExist(){
+  isLegalNoticeLinkExist() {
     const currSet = Template.instance().currentSetting.get();
     const currSet = Template.instance().currentSetting.get();
-    if(currSet && currSet !== undefined && currSet != null){
+    if (currSet && currSet !== undefined && currSet != null) {
       return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
       return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
     }
     }
     else
     else
       return false;
       return false;
   },
   },
 
 
-  getLegalNoticeWithWritTraduction(){
+  getLegalNoticeWithWritTraduction() {
     let spanLegalNoticeElt = $("#legalNoticeSpan");
     let spanLegalNoticeElt = $("#legalNoticeSpan");
-    if(spanLegalNoticeElt != null && spanLegalNoticeElt != undefined){
+    if (spanLegalNoticeElt != null && spanLegalNoticeElt != undefined) {
       spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}));
       spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}));
     }
     }
     let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
     let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
-    if(atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined){
+    if (atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined) {
       atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}));
       atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}));
     }
     }
     return true;
     return true;
@@ -147,41 +159,41 @@ Template.userFormsLayout.events({
     }
     }
     isCheckDone = false;
     isCheckDone = false;
   },
   },
-  'click #at-signUp'(event, templateInstance){
+  'click #at-signUp'(event, templateInstance) {
     isCheckDone = false;
     isCheckDone = false;
   },
   },
-  'DOMSubtreeModified #at-oidc'(event){
-    if(alreadyCheck <= 2){
+  'DOMSubtreeModified #at-oidc'(event) {
+    if (alreadyCheck <= 2) {
       let currSetting = Settings.findOne();
       let currSetting = Settings.findOne();
       let oidcBtnElt = $("#at-oidc");
       let oidcBtnElt = $("#at-oidc");
-      if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
+      if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
         let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
         let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
-        if(alreadyCheck == 1){
+        if (alreadyCheck == 1) {
           alreadyCheck++;
           alreadyCheck++;
           oidcBtnElt.html("");
           oidcBtnElt.html("");
         }
         }
-        else{
+        else {
           alreadyCheck++;
           alreadyCheck++;
           oidcBtnElt.html(htmlvalue);
           oidcBtnElt.html(htmlvalue);
         }
         }
       }
       }
     }
     }
-    else{
+    else {
       alreadyCheck = 1;
       alreadyCheck = 1;
     }
     }
   },
   },
-  'DOMSubtreeModified .at-form'(event){
-    if(alreadyCheck <= 2 && !isCheckDone){
-      if(document.getElementById("at-oidc") != null){
+  'DOMSubtreeModified .at-form'(event) {
+    if (alreadyCheck <= 2 && !isCheckDone) {
+      if (document.getElementById("at-oidc") != null) {
         let currSetting = Settings.findOne();
         let currSetting = Settings.findOne();
         let oidcBtnElt = $("#at-oidc");
         let oidcBtnElt = $("#at-oidc");
-        if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
+        if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
           let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
           let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
-          if(alreadyCheck == 1){
+          if (alreadyCheck == 1) {
             alreadyCheck++;
             alreadyCheck++;
             oidcBtnElt.html("");
             oidcBtnElt.html("");
           }
           }
-          else{
+          else {
             alreadyCheck++;
             alreadyCheck++;
             isCheckDone = true;
             isCheckDone = true;
             oidcBtnElt.html(htmlvalue);
             oidcBtnElt.html(htmlvalue);
@@ -189,7 +201,7 @@ Template.userFormsLayout.events({
         }
         }
       }
       }
     }
     }
-    else{
+    else {
       alreadyCheck = 1;
       alreadyCheck = 1;
     }
     }
   },
   },
@@ -221,7 +233,7 @@ async function authentication(event, templateInstance) {
   switch (result) {
   switch (result) {
     case 'ldap':
     case 'ldap':
       return new Promise(resolve => {
       return new Promise(resolve => {
-        Meteor.loginWithLDAP(match, password, function() {
+        Meteor.loginWithLDAP(match, password, function () {
           resolve(FlowRouter.go('/'));
           resolve(FlowRouter.go('/'));
         });
         });
       });
       });
@@ -233,7 +245,7 @@ async function authentication(event, templateInstance) {
           {
           {
             provider,
             provider,
           },
           },
-          function() {
+          function () {
             resolve(FlowRouter.go('/'));
             resolve(FlowRouter.go('/'));
           },
           },
         );
         );
@@ -241,7 +253,7 @@ async function authentication(event, templateInstance) {
 
 
     case 'cas':
     case 'cas':
       return new Promise(resolve => {
       return new Promise(resolve => {
-        Meteor.loginWithCas(match, password, function() {
+        Meteor.loginWithCas(match, password, function () {
           resolve(FlowRouter.go('/'));
           resolve(FlowRouter.go('/'));
         });
         });
       });
       });
@@ -267,7 +279,6 @@ function getUserAuthenticationMethod(defaultAuthenticationMethod, match) {
       Meteor.subscribe('user-authenticationMethod', match, {
       Meteor.subscribe('user-authenticationMethod', match, {
         onReady() {
         onReady() {
           const user = Users.findOne();
           const user = Users.findOne();
-
           const authenticationMethod = user
           const authenticationMethod = user
             ? user.authenticationMethod
             ? user.authenticationMethod
             : defaultAuthenticationMethod;
             : defaultAuthenticationMethod;

+ 35 - 5
config/accounts.js

@@ -5,6 +5,16 @@ const emailField = AccountsTemplates.removeField('email');
 let disableRegistration = false;
 let disableRegistration = false;
 let disableForgotPassword = false;
 let disableForgotPassword = false;
 let passwordLoginDisabled = false;
 let passwordLoginDisabled = false;
+let oidcRedirectionEnabled = false;
+let oauthServerUrl = "home";
+let oauthDashboardUrl = "";
+
+Meteor.call('isOidcRedirectionEnabled', (_, result) => {
+  if(result)
+  {
+    oidcRedirectionEnabled = true;
+  }
+});
 
 
 Meteor.call('isPasswordLoginDisabled', (_, result) => {
 Meteor.call('isPasswordLoginDisabled', (_, result) => {
   if (result) {
   if (result) {
@@ -14,6 +24,18 @@ Meteor.call('isPasswordLoginDisabled', (_, result) => {
   }
   }
 });
 });
 
 
+Meteor.call('getOauthServerUrl', (_, result) => {
+  if (result) {
+    oauthServerUrl = result;
+  }
+});
+
+Meteor.call('getOauthDashboardUrl', (_, result) => {
+  if (result) {
+    oauthDashboardUrl = result;
+  }
+});
+
 Meteor.call('isDisableRegistration', (_, result) => {
 Meteor.call('isDisableRegistration', (_, result) => {
   if (result) {
   if (result) {
     disableRegistration = true;
     disableRegistration = true;
@@ -59,11 +81,19 @@ AccountsTemplates.configure({
   showForgotPasswordLink: !disableForgotPassword,
   showForgotPasswordLink: !disableForgotPassword,
   forbidClientAccountCreation: disableRegistration,
   forbidClientAccountCreation: disableRegistration,
   onLogoutHook() {
   onLogoutHook() {
-    const homePage = 'home';
-    if (FlowRouter.getRouteName() === homePage) {
-      FlowRouter.reload();
-    } else {
-      FlowRouter.go(homePage);
+    // here comeslogic for redirect
+    if(oidcRedirectionEnabled)
+    {
+      window.location = oauthServerUrl + oauthDashboardUrl;
+    }
+    else
+    {
+      const homePage = 'home';
+      if (FlowRouter.getRouteName() === homePage) {
+        FlowRouter.reload();
+      } else {
+        FlowRouter.go(homePage);
+      }
     }
     }
   },
   },
 });
 });

+ 18 - 0
models/settings.js

@@ -229,6 +229,12 @@ if (Meteor.isServer) {
     ]);
     ]);
   }
   }
 
 
+  function loadOidcConfig(service){
+    check(service, String);
+    var config = ServiceConfiguration.configurations.findOne({service: service});
+    return config;
+  }
+
   function sendInvitationEmail(_id) {
   function sendInvitationEmail(_id) {
     const icode = InvitationCodes.findOne(_id);
     const icode = InvitationCodes.findOne(_id);
     const author = Users.findOne(Meteor.userId());
     const author = Users.findOne(Meteor.userId());
@@ -495,6 +501,12 @@ if (Meteor.isServer) {
       };
       };
     },
     },
 
 
+    getOauthServerUrl(){
+      return process.env.OAUTH2_SERVER_URL;
+    },
+    getOauthDashboardUrl(){
+      return process.env.DASHBOARD_URL;
+    },
     getDefaultAuthenticationMethod() {
     getDefaultAuthenticationMethod() {
       return process.env.DEFAULT_AUTHENTICATION_METHOD;
       return process.env.DEFAULT_AUTHENTICATION_METHOD;
     },
     },
@@ -502,6 +514,12 @@ if (Meteor.isServer) {
     isPasswordLoginDisabled() {
     isPasswordLoginDisabled() {
       return process.env.PASSWORD_LOGIN_ENABLED === 'false';
       return process.env.PASSWORD_LOGIN_ENABLED === 'false';
     },
     },
+    isOidcRedirectionEnabled(){
+      return process.env.OIDC_REDIRECTION_ENABLED === 'true' && Object.keys(loadOidcConfig("oidc")).length > 0;
+    },
+    getServiceConfiguration(service){
+      return loadOidcConfig(service);
+      }
   });
   });
 }
 }
 
 

+ 2 - 2
packages/wekan-accounts-oidc/oidc.js

@@ -7,11 +7,11 @@ if (Meteor.isClient) {
       callback = options;
       callback = options;
       options = null;
       options = null;
     }
     }
-
     var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
     var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
     Oidc.requestCredential(options, credentialRequestCompleteCallback);
     Oidc.requestCredential(options, credentialRequestCompleteCallback);
   };
   };
-} else {
+  }
+  else {
   Accounts.addAutopublishFields({
   Accounts.addAutopublishFields({
     // not sure whether the OIDC api can be used from the browser,
     // not sure whether the OIDC api can be used from the browser,
     // thus not sure if we should be sending access tokens; but we do it
     // thus not sure if we should be sending access tokens; but we do it

+ 22 - 2
packages/wekan-oidc/README.md

@@ -47,6 +47,26 @@ See example below:
 
 
   NOTE: orgs & teams won't be updated if they already exist.
   NOTE: orgs & teams won't be updated if they already exist.
 
 
-  5. Manages admin rights as well. If user is in Group which has isAdmin: set to true, user will get admin 
-     privileges in Wekan as well. 
+  5. Manages admin rights as well. If user is in Group which has isAdmin: set to true, user will get admin
+     privileges in Wekan as well.
      If no adjustments (e.g. 1-3) are made on oidc provider's side, user will receive his/her admin rights from before.
      If no adjustments (e.g. 1-3) are made on oidc provider's side, user will receive his/her admin rights from before.
+
+## For further empowerment of oidc as sso solution
+
+If you want to be redirected to your oidc provider on LOGIN without going the extra loop of signing in.
+On LOGOUT you will be redirected to the oidc provider as well.
+
+Add to your .env file:
+
+OIDC_REDIRECTION_ENABLED=true
+OAUTH2_SERVER_URL=http://localhost:9000
+DASHBOARD_URL=/if/session-end/wekan/
+
+Example for authentik.
+The latter specifies the OIDC Dashboard you'll get redirected on logout
+
+Flow:
+You need to have an oidc provider configured to get this feature
+Make sure to have
+Authorize Application (default-provider-authorization-implicit-consent)
+enabled

+ 49 - 44
packages/wekan-oidc/oidc_client.js

@@ -12,56 +12,61 @@ Oidc.requestCredential = function (options, credentialRequestCompleteCallback) {
     options = {};
     options = {};
   }
   }
 
 
-  var config = ServiceConfiguration.configurations.findOne({service: 'oidc'});
-  if (!config) {
-    credentialRequestCompleteCallback && credentialRequestCompleteCallback(
-      new ServiceConfiguration.ConfigError('Service oidc not configured.'));
-    return;
-  }
+  Meteor.call("getServiceConfiguration", "oidc",(_, result) => {
+    if (result) {
+      var config = result;
+      var credentialToken = Random.secret();
+      var loginStyle = OAuth._loginStyle('oidc', config, options);
+      // options
+      options = options || {};
+      options.client_id = config.clientId;
+      options.response_type = options.response_type || 'code';
+      options.redirect_uri = OAuth._redirectUri('oidc', config);
+      options.state = OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl);
+      options.scope = config.requestPermissions || 'openid profile email';
 
 
-  var credentialToken = Random.secret();
-  var loginStyle = OAuth._loginStyle('oidc', config, options);
+      if (config.loginStyle && config.loginStyle == 'popup') {
+        options.display = 'popup';
+      }
 
 
-  // options
-  options = options || {};
-  options.client_id = config.clientId;
-  options.response_type = options.response_type || 'code';
-  options.redirect_uri = OAuth._redirectUri('oidc', config);
-  options.state = OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl);
-  options.scope = config.requestPermissions || 'openid profile email';
+      var loginUrl = config.serverUrl + config.authorizationEndpoint;
+      // check if the loginUrl already contains a "?"
+      var first = loginUrl.indexOf('?') === -1;
+      for (var k in options) {
+        if (first) {
+          loginUrl += '?';
+          first = false;
+        }
+        else {
+          loginUrl += '&'
+        }
+        loginUrl += encodeURIComponent(k) + '=' + encodeURIComponent(options[k]);
+      }
 
 
-  if (config.loginStyle && config.loginStyle == 'popup') {
-    options.display = 'popup';
-  }
+      //console.log('XXX: loginURL: ' + loginUrl)
 
 
-  var loginUrl = config.serverUrl + config.authorizationEndpoint;
-  // check if the loginUrl already contains a "?"
-  var first = loginUrl.indexOf('?') === -1;
-  for (var k in options) {
-    if (first) {
-      loginUrl += '?';
-      first = false;
+      options.popupOptions = options.popupOptions || {};
+      var popupOptions = {
+        width:  options.popupOptions.width || 320,
+        height: options.popupOptions.height || 450
+      };
+
+      OAuth.launchLogin({
+        loginService: 'oidc',
+        loginStyle: loginStyle,
+        loginUrl: loginUrl,
+        credentialRequestCompleteCallback: credentialRequestCompleteCallback,
+        credentialToken: credentialToken,
+        popupOptions: popupOptions,
+      });
     }
     }
-    else {
-      loginUrl += '&'
+    else
+    {
+      credentialRequestCompleteCallback && credentialRequestCompleteCallback(
+        new ServiceConfiguration.ConfigError('Service oidc not configured.'));
+      return;
     }
     }
-    loginUrl += encodeURIComponent(k) + '=' + encodeURIComponent(options[k]);
-  }
-
-  //console.log('XXX: loginURL: ' + loginUrl)
+  });
 
 
-  options.popupOptions = options.popupOptions || {};
-  var popupOptions = {
-    width:  options.popupOptions.width || 320,
-    height: options.popupOptions.height || 450
-  };
 
 
-  OAuth.launchLogin({
-    loginService: 'oidc',
-    loginStyle: loginStyle,
-    loginUrl: loginUrl,
-    credentialRequestCompleteCallback: credentialRequestCompleteCallback,
-    credentialToken: credentialToken,
-    popupOptions: popupOptions,
-  });
 };
 };

+ 0 - 1
packages/wekan-oidc/oidc_server.js

@@ -19,7 +19,6 @@ var serviceData = {};
 var userinfo = {};
 var userinfo = {};
 
 
 OAuth.registerService('oidc', 2, null, function (query) {
 OAuth.registerService('oidc', 2, null, function (query) {
-
   var debug = process.env.DEBUG || false;
   var debug = process.env.DEBUG || false;
 
 
   var token = getToken(query);
   var token = getToken(query);

+ 1 - 1
server/authentication.js

@@ -108,7 +108,7 @@ Meteor.startup(() => {
           // OAUTH2_ID_TOKEN_WHITELIST_FIELDS || [],
           // OAUTH2_ID_TOKEN_WHITELIST_FIELDS || [],
           // OAUTH2_REQUEST_PERMISSIONS || 'openid profile email',
           // OAUTH2_REQUEST_PERMISSIONS || 'openid profile email',
         },
         },
-      );
+        );
     } else if (
     } else if (
       process.env.CAS_ENABLED === 'true' ||
       process.env.CAS_ENABLED === 'true' ||
       process.env.CAS_ENABLED === true
       process.env.CAS_ENABLED === true