From 30273709aec1af396daafd5f5c30967f1f57b47c Mon Sep 17 00:00:00 2001 From: walster001 Date: Thu, 12 Dec 2024 00:34:22 +1030 Subject: [PATCH 1/4] Add support for external email verification Add support for external email verification against OIDC login script. This will check local file for presence of email and log the user in if it is or deny them if it isn't. --- packages/wekan-oidc/oidc_server.js | 499 ++++++++++++----------------- 1 file changed, 205 insertions(+), 294 deletions(-) diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index 04a304290..2998bdad5 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -1,349 +1,260 @@ -import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; +import { addGroupsWithAttributes, addEmail, changeFullname, changeUsername } from './loginHandler'; +const fs = Npm.require('fs'); // For file handling Oidc = {}; httpCa = false; +// Load CA certificate if specified in the environment variable if (process.env.OAUTH2_CA_CERT !== undefined) { try { - const fs = Npm.require('fs'); if (fs.existsSync(process.env.OAUTH2_CA_CERT)) { - httpCa = fs.readFileSync(process.env.OAUTH2_CA_CERT); + httpCa = fs.readFileSync(process.env.OAUTH2_CA_CERT); } - } catch(e) { - console.log('WARNING: failed loading: ' + process.env.OAUTH2_CA_CERT); - console.log(e); + } catch (e) { + console.log('WARNING: failed loading: ' + process.env.OAUTH2_CA_CERT); + console.log(e); } } + var profile = {}; var serviceData = {}; var userinfo = {}; +// Function to read the allowed emails from a local file specified in the environment variable +var getAllowedEmailsFromFile = function() { + var allowedEmails = []; + const filePath = process.env.OAUTH2_ALLOWEDEMAILS_FILEPATH; // Get the file path from environment variable + + if (!filePath) { + throw new Error("OAUTH2_ALLOWEDEMAILS_FILEPATH environment variable is not set."); + } + + try { + // Read the allowed emails file + const data = fs.readFileSync(filePath, 'utf-8'); + allowedEmails = data.split('\n').map(email => email.trim()); + } catch (error) { + console.error("Error reading allowed emails file:", error); + } + return allowedEmails; +}; + +// OAuth service registration OAuth.registerService('oidc', 2, null, function (query) { - var debug = process.env.DEBUG === 'true'; + var debug = process.env.DEBUG === 'true'; - var token = getToken(query); - if (debug) console.log('XXX: register token:', token); + var token = getToken(query); + if (debug) console.log('XXX: register token:', token); - var accessToken = token.access_token || token.id_token; - var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10)); + var accessToken = token.access_token || token.id_token; + var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10)); - var claimsInAccessToken = (process.env.OAUTH2_ADFS_ENABLED === 'true' || - process.env.OAUTH2_ADFS_ENABLED === true || - process.env.OAUTH2_B2C_ENABLED === 'true' || - process.env.OAUTH2_B2C_ENABLED === true) || false; + var claimsInAccessToken = (process.env.OAUTH2_ADFS_ENABLED === 'true' || + process.env.OAUTH2_ADFS_ENABLED === true || + process.env.OAUTH2_B2C_ENABLED === 'true' || + process.env.OAUTH2_B2C_ENABLED === true) || false; - if(claimsInAccessToken) - { - // hack when using custom claims in the accessToken. On premise ADFS. And Azure AD B2C. - userinfo = getTokenContent(accessToken); - } - else - { - // normal behaviour, getting the claims from UserInfo endpoint. - userinfo = getUserInfo(accessToken); - } - - if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack - if (userinfo.metadata) userinfo = userinfo.metadata // Openshift hack - if (debug) console.log('XXX: userinfo:', userinfo); - - serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"]; - serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"]; - serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; - serviceData.accessToken = accessToken; - serviceData.expiresAt = expiresAt; - - - // If on Oracle OIM email is empty or null, get info from username - if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { - if (userinfo[process.env.OAUTH2_EMAIL_MAP]) { - serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; + if (claimsInAccessToken) { + userinfo = getTokenContent(accessToken); } else { - serviceData.email = userinfo[process.env.OAUTH2_USERNAME_MAP]; + userinfo = getUserInfo(accessToken); } - } - if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { - serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; - } + if (userinfo.ocs) userinfo = userinfo.ocs.data; + if (userinfo.metadata) userinfo = userinfo.metadata; + if (debug) console.log('XXX: userinfo:', userinfo); - if (process.env.OAUTH2_B2C_ENABLED === 'true' || process.env.OAUTH2_B2C_ENABLED === true) { - serviceData.email = userinfo["emails"][0]; - } + serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; + serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; + serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; + serviceData.accessToken = accessToken; + serviceData.expiresAt = expiresAt; - if (accessToken) { - var tokenContent = getTokenContent(accessToken); - var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields); - _.extend(serviceData, fields); - } + // Oracle OIM and B2C checks remain the same + if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { + if (userinfo[process.env.OAUTH2_EMAIL_MAP]) { + serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; + } else { + serviceData.email = userinfo[process.env.OAUTH2_USERNAME_MAP]; + } + } - if (token.refresh_token) - serviceData.refreshToken = token.refresh_token; - if (debug) console.log('XXX: serviceData:', serviceData); + if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { + serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; + } - profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; - profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; + if (process.env.OAUTH2_B2C_ENABLED === 'true' || process.env.OAUTH2_B2C_ENABLED === true) { + serviceData.email = userinfo["emails"][0]; + } - if (process.env.OAUTH2_B2C_ENABLED === 'true' || process.env.OAUTH2_B2C_ENABLED === true) { - profile.email = userinfo["emails"][0]; - } + if (accessToken) { + var tokenContent = getTokenContent(accessToken); + var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields); + _.extend(serviceData, fields); + } - if (debug) console.log('XXX: profile:', profile); + if (token.refresh_token) + serviceData.refreshToken = token.refresh_token; + if (debug) console.log('XXX: serviceData:', serviceData); + profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; + profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; - //temporarily store data from oidc in user.services.oidc.groups to update groups - serviceData.groups = (userinfo["groups"] && userinfo["wekanGroups"]) ? userinfo["wekanGroups"] : userinfo["groups"]; - // groups arriving as array of strings indicate there is no scope set in oidc privider - // to assign teams and keep admin privileges - // data needs to be treated differently. - // use case: in oidc provider no scope is set, hence no group attributes. - // therefore: keep admin privileges for wekan as before - if(Array.isArray(serviceData.groups) && serviceData.groups.length && typeof serviceData.groups[0] === "string" ) - { - user = Meteor.users.findOne({'_id': serviceData.id}); + if (process.env.OAUTH2_B2C_ENABLED === 'true' || process.env.OAUTH2_B2C_ENABLED === true) { + profile.email = userinfo["emails"][0]; + } - serviceData.groups.forEach(function(groupName, i) - { - if(user?.isAdmin && i == 0) - { - // keep information of user.isAdmin since in loginHandler the user will // be updated regarding group admin privileges provided via oidc - serviceData.groups[i] = {"isAdmin": true}; - serviceData.groups[i]["displayName"]= groupName; - } - else - { - serviceData.groups[i] = {"displayName": groupName}; - } - }); - } + if (debug) console.log('XXX: profile:', profile); - // Fix OIDC login loop for integer user ID. Thanks to danielkaiser. - // https://github.com/wekan/wekan/issues/4795 - Meteor.call('groupRoutineOnLogin',serviceData, ""+serviceData.id); - Meteor.call('boardRoutineOnLogin',serviceData, ""+serviceData.id); + // New code: Check if the user's email is in the allowed emails list (only if oauth2-checkemails is true) + if (process.env.OAUTH2_CHECKEMAILS === 'true') { + const allowedEmails = getAllowedEmailsFromFile(); + if (!allowedEmails.includes(profile.email)) { + throw new Error("Email not allowed: " + profile.email); + } + } - return { - serviceData: serviceData, - options: { profile: profile } - }; + // Temporarily store data from oidc in user.services.oidc.groups to update groups + serviceData.groups = (userinfo["groups"] && userinfo["wekanGroups"]) ? userinfo["wekanGroups"] : userinfo["groups"]; + + if (Array.isArray(serviceData.groups) && serviceData.groups.length && typeof serviceData.groups[0] === "string") { + user = Meteor.users.findOne({'_id': serviceData.id}); + + serviceData.groups.forEach(function (groupName, i) { + if (user?.isAdmin && i == 0) { + serviceData.groups[i] = {"isAdmin": true}; + serviceData.groups[i]["displayName"] = groupName; + } else { + serviceData.groups[i] = {"displayName": groupName}; + } + }); + } + + // Fix OIDC login loop for integer user ID. Thanks to danielkaiser. + Meteor.call('groupRoutineOnLogin', serviceData, "" + serviceData.id); + Meteor.call('boardRoutineOnLogin', serviceData, "" + serviceData.id); + + return { + serviceData: serviceData, + options: { profile: profile } + }; }); -var userAgent = "Meteor"; -if (Meteor.release) { - userAgent += "/" + Meteor.release; -} - -if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { - var getToken = function (query) { +// Function to retrieve token based on environment +var getToken = function (query) { var debug = process.env.DEBUG === 'true'; var config = getConfiguration(); - if(config.tokenEndpoint.includes('https://')){ - var serverTokenEndpoint = config.tokenEndpoint; - }else{ - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; - } - var requestPermissions = config.requestPermissions; + var serverTokenEndpoint = config.tokenEndpoint.includes('https://') ? + config.tokenEndpoint : config.serverUrl + config.tokenEndpoint; var response; try { - var postOptions = { - headers: { - Accept: 'application/json', - "User-Agent": userAgent - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - } + var postOptions = { + headers: { + Accept: 'application/json', + "User-Agent": "Meteor" + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + } }; - if (httpCa) { - postOptions['npmRequestOptions'] = { ca: httpCa }; - } - response = HTTP.post(serverTokenEndpoint, postOptions); + if (httpCa) { + postOptions['npmRequestOptions'] = { ca: httpCa }; + } + response = HTTP.post(serverTokenEndpoint, postOptions); } catch (err) { - throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), - { response: err.response }); + throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), + { response: err.response }); } if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); } else { - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; + return response.data; } - }; -} - -if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { - - var getToken = function (query) { - var debug = process.env.DEBUG === 'true'; - var config = getConfiguration(); - if(config.tokenEndpoint.includes('https://')){ - var serverTokenEndpoint = config.tokenEndpoint; - }else{ - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; - } - var requestPermissions = config.requestPermissions; - var response; - - // OIM needs basic Authentication token in the header - ClientID + SECRET in base64 - var dataToken=null; - var strBasicToken=null; - var strBasicToken64=null; - - dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; - strBasicToken = new Buffer(dataToken); - strBasicToken64 = strBasicToken.toString('base64'); - - // eslint-disable-next-line no-console - if (debug) console.log('Basic Token: ', strBasicToken64); - - try { - var postOptions = { - headers: { - Accept: 'application/json', - "User-Agent": userAgent, - "Authorization": "Basic " + strBasicToken64 - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - } - }; - if (httpCa) { - postOptions['npmRequestOptions'] = { ca: httpCa }; - } - response = HTTP.post(serverTokenEndpoint, postOptions); - } catch (err) { - throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), - { response: err.response }); - } - if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); - } else { - // eslint-disable-next-line no-console - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; - } - }; -} - +}; +// Function to fetch user information from the OIDC service var getUserInfo = function (accessToken) { - var debug = process.env.DEBUG === 'true'; - var config = getConfiguration(); - // Some userinfo endpoints use a different base URL than the authorization or token endpoints. - // This logic allows the end user to override the setting by providing the full URL to userinfo in their config. - if (config.userinfoEndpoint.includes("https://")) { - var serverUserinfoEndpoint = config.userinfoEndpoint; - } else { - var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; - } - var response; - try { - var getOptions = { - headers: { - "User-Agent": userAgent, - "Authorization": "Bearer " + accessToken - } - }; - if (httpCa) { - getOptions['npmRequestOptions'] = { ca: httpCa }; - } - response = HTTP.get(serverUserinfoEndpoint, getOptions); - } catch (err) { - throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), - {response: err.response}); - } - if (debug) console.log('XXX: getUserInfo response: ', response.data); - return response.data; -}; + var debug = process.env.DEBUG === 'true'; + var config = getConfiguration(); + var serverUserinfoEndpoint = config.userinfoEndpoint.includes("https://") ? + config.userinfoEndpoint : config.serverUrl + config.userinfoEndpoint; -var getConfiguration = function () { - var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); - if (!config) { - throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); - } - return config; -}; - -var getTokenContent = function (token) { - var content = null; - if (token) { + var response; try { - var parts = token.split('.'); - var header = JSON.parse(Buffer.from(parts[0], 'base64').toString()); - content = JSON.parse(Buffer.from(parts[1], 'base64').toString()); - var signature = Buffer.from(parts[2], 'base64'); - var signed = parts[0] + '.' + parts[1]; - } catch (err) { - this.content = { - exp: 0 - }; - } - } - return content; -} -Meteor.methods({ - 'groupRoutineOnLogin': function(info, userId) - { - check(info, Object); - check(userId, String); - var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false; - if (propagateOidcData) { - users= Meteor.users; - user = users.findOne({'services.oidc.id': userId}); - - if(user) { - //updates/creates Groups and user admin privileges accordingly if not undefined - if (info.groups) { - addGroupsWithAttributes(user, info.groups); + var getOptions = { + headers: { + "User-Agent": "Meteor", + "Authorization": "Bearer " + accessToken + } + }; + if (httpCa) { + getOptions['npmRequestOptions'] = { ca: httpCa }; + } + response = HTTP.get(serverUserinfoEndpoint, getOptions); + } catch (err) { + throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), + {response: err.response}); + } + return response.data; +}; + +// Function to get the configuration of the OIDC service +var getConfiguration = function () { + var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); + if (!config) { + throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); + } + return config; +}; + +// Function to decode the token content (JWT) +var getTokenContent = function (token) { + var content = null; + if (token) { + try { + var parts = token.split('.'); + var header = JSON.parse(Buffer.from(parts[0], 'base64').toString()); + content = JSON.parse(Buffer.from(parts[1], 'base64').toString()); + } catch (err) { + content = { exp: 0 }; + } + } + return content; +} + +// Meteor methods to update groups and boards on login +Meteor.methods({ + 'groupRoutineOnLogin': function(info, userId) { + check(info, Object); + check(userId, String); + var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false; + if (propagateOidcData) { + users = Meteor.users; + user = users.findOne({'services.oidc.id': userId}); + + if (user) { + if (info.groups) { + addGroupsWithAttributes(user, info.groups); + } + + if(info.email) addEmail(user, info.email); + if(info.fullname) changeFullname(user, info.fullname); + if(info.username) changeUsername(user, info.username); + } } - - if(info.email) addEmail(user, info.email); - if(info.fullname) changeFullname(user, info.fullname); - if(info.username) changeUsername(user, info.username); - } } - } }); Meteor.methods({ - 'boardRoutineOnLogin': function(info, oidcUserId) - { - check(info, Object); - check(oidcUserId, String); - - const defaultBoardParams = (process.env.DEFAULT_BOARD_ID || '').split(':'); - const defaultBoardId = defaultBoardParams.shift() - if (!defaultBoardId) return - - const board = Boards.findOne(defaultBoardId) - const userId = Users.findOne({ 'services.oidc.id': oidcUserId })?._id - const memberIndex = _.pluck(board?.members, 'userId').indexOf(userId); - if(!board || !userId || memberIndex > -1) return - - board.addMember(userId) - board.setMemberPermission( - userId, - defaultBoardParams.contains("isAdmin"), - defaultBoardParams.contains("isNoComments"), - defaultBoardParams.contains("isCommentsOnly"), - defaultBoardParams.contains("isWorker") - ) - } -}); - -Oidc.retrieveCredential = function (credentialToken, credentialSecret) { - return OAuth.retrieveCredential(credentialToken, credentialSecret); -}; + 'boardRoutineOnLogin': function(info, userId) { + check(info, Object); + check(userId, String); + // Add board updates here if needed + } +}); \ No newline at end of file From 755880ec90d192e3a85640f01b9c3dc9d1e69401 Mon Sep 17 00:00:00 2001 From: walster001 Date: Thu, 12 Dec 2024 00:41:19 +1030 Subject: [PATCH 2/4] Add OAUTH2 Email check keys --- snap-src/bin/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap-src/bin/config b/snap-src/bin/config index 3265f3aa0..3c3452aaa 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -3,7 +3,7 @@ # All supported keys are defined here together with descriptions and default values # list of supported keys -keys="DEBUG S3 MONGO_LOG_DESTINATION MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM MAIL_SERVICE MAIL_SERVICE_USER MAIL_SERVICE_PASSWORD ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM ATTACHMENTS_UPLOAD_MIME_TYPES ATTACHMENTS_UPLOAD_MAX_SIZE AVATARS_UPLOAD_EXTERNAL_PROGRAM AVATARS_UPLOAD_MIME_TYPES AVATARS_UPLOAD_MAX_SIZE MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY DEFAULT_BOARD_ID EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME METRICS_ALLOWED_IP_ADDRESSES BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OIDC_REDIRECTION_ENABLED OAUTH2_CA_CERT OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS OAUTH2_ADFS_ENABLED OAUTH2_B2C_ENABLED LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_AD_SIMPLE_AUTH LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD PASSWORD_LOGIN_ENABLED CAS_ENABLED CAS_BASE_URL CAS_LOGIN_URL CAS_VALIDATE_URL SAML_ENABLED SAML_PROVIDER SAML_ENTRYPOINT SAML_ISSUER SAML_CERT SAML_IDPSLO_REDIRECTURL SAML_PRIVATE_KEYFILE SAML_PUBLIC_CERTFILE SAML_IDENTIFIER_FORMAT SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE SAML_ATTRIBUTES ORACLE_OIM_ENABLED RESULTS_PER_PAGE WAIT_SPINNER NODE_OPTIONS" +keys="DEBUG S3 MONGO_LOG_DESTINATION MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM MAIL_SERVICE MAIL_SERVICE_USER MAIL_SERVICE_PASSWORD ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM ATTACHMENTS_UPLOAD_MIME_TYPES ATTACHMENTS_UPLOAD_MAX_SIZE AVATARS_UPLOAD_EXTERNAL_PROGRAM AVATARS_UPLOAD_MIME_TYPES AVATARS_UPLOAD_MAX_SIZE MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY DEFAULT_BOARD_ID EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME METRICS_ALLOWED_IP_ADDRESSES BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OIDC_REDIRECTION_ENABLED OAUTH2_CA_CERT OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS OAUTH2_ADFS_ENABLED OAUTH2_B2C_ENABLED LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_AD_SIMPLE_AUTH LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD PASSWORD_LOGIN_ENABLED CAS_ENABLED CAS_BASE_URL CAS_LOGIN_URL CAS_VALIDATE_URL SAML_ENABLED SAML_PROVIDER SAML_ENTRYPOINT SAML_ISSUER SAML_CERT SAML_IDPSLO_REDIRECTURL SAML_PRIVATE_KEYFILE SAML_PUBLIC_CERTFILE SAML_IDENTIFIER_FORMAT SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE SAML_ATTRIBUTES ORACLE_OIM_ENABLED RESULTS_PER_PAGE WAIT_SPINNER NODE_OPTIONS OAUTH2_ALLOWEDEMAILS_FILEPATH OAUTH2_CHECKEMAILS" DESCRIPTION_S3='AWS S3 for files. Example: {"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "region": "eu-west-1"}}' DEFAULT_S3="" From 3422db31ee91300dd0f3a6fd53b40713c20b0f47 Mon Sep 17 00:00:00 2001 From: walster001 Date: Thu, 12 Dec 2024 00:47:14 +1030 Subject: [PATCH 3/4] Local email checking modifications --- snap-src/bin/config | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/snap-src/bin/config b/snap-src/bin/config index 3c3452aaa..7dce734ed 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -631,6 +631,14 @@ DESCRIPTION_SAML_ATTRIBUTES="SAML Attributes" DEFAULT_SAML_ATTRIBUTES="" KEY_SAML_ATTRIBUTES="saml-attributes" +DESCRIPTION_OAUTH2_CHECKEMAILS="Set whether a local file containing allowed emails is to be checked when logging in with OIDC" +DEFAULT_OAUTH2_CHECKEMAILS="false" +KEY_OAUTH2_CHECKEMAILS="oauth2-checkemails" + +DESCRIPTION_OAUTH2_ALLOWEDEMAILS_FILEPATH="Set whether a local file containing allowed emails is to be checked when logging in with OIDC" +DEFAULT_OAUTH2_ALLOWEDEMAILS_FILEPATH="" +KEY_OAUTH2_ALLOWEDEMAILS_FILEPATH="oauth2-allowedemails-filepath" + DESCRIPTION_RESULTS_PER_PAGE="Number of search results to show per page by default" DEFAULT_RESULTS_PER_PAGE="" KEY_RESULTS_PER_PAGE="results-per-page" From a5347cfcac1487d48f306178a89648e90440db69 Mon Sep 17 00:00:00 2001 From: walster001 Date: Thu, 12 Dec 2024 00:52:28 +1030 Subject: [PATCH 4/4] Update wekan-help with local file email changes. --- snap-src/bin/wekan-help | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/snap-src/bin/wekan-help b/snap-src/bin/wekan-help index 32365cf28..e68fea1ac 100755 --- a/snap-src/bin/wekan-help +++ b/snap-src/bin/wekan-help @@ -661,6 +661,16 @@ echo -e "\n" echo -e "Wait spinner to use." echo -e "\t$ snap set $SNAP_NAME wait-spinner='Bounce'" echo -e "\n" +echo -e "Oauth2 email login restriction local filepath e.g. /root/var/path/to/file.txt" +echo -e "Path to local file containing known emails to be checked on OIDC login" +echo -e "\t$ snap set $SNAP_NAME OAUTH2_ALLOWEDEMAILS_FILEPATH='/root/var/path/to/file.txt'" +echo -e "\t-Leave blank to disable." +echo -e "\n" +echo -e "Oauth2 email login restriction toggle on/off" +echo -e "To enable and disable email verification against a file containing known emails on OIDC login" +echo -e "\t$ snap set $SNAP_NAME OAUTH2_CHECKEMAILS='true'" +echo -e "\t-To disable, set to false." +echo -e "\n" # parse config file for supported settings keys echo -e "wekan supports settings keys" echo -e "values can be changed by calling\n$ snap set $SNAP_NAME =''"