oidc_server.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import {addGroups, addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler';
  2. Oidc = {};
  3. httpCa = false;
  4. if (process.env.OAUTH2_CA_CERT !== undefined) {
  5. try {
  6. const fs = Npm.require('fs');
  7. if (fs.existsSync(process.env.OAUTH2_CA_CERT)) {
  8. httpCa = fs.readFileSync(process.env.OAUTH2_CA_CERT);
  9. }
  10. } catch(e) {
  11. console.log('WARNING: failed loading: ' + process.env.OAUTH2_CA_CERT);
  12. console.log(e);
  13. }
  14. }
  15. OAuth.registerService('oidc', 2, null, function (query) {
  16. var debug = process.env.DEBUG || false;
  17. var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false;
  18. var token = getToken(query);
  19. if (debug) console.log('XXX: register token:', token);
  20. var accessToken = token.access_token || token.id_token;
  21. var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10));
  22. var claimsInAccessToken = (process.env.OAUTH2_ADFS_ENABLED === 'true' || process.env.OAUTH2_ADFS_ENABLED === true) || false;
  23. var userinfo;
  24. if(claimsInAccessToken)
  25. {
  26. // hack when using custom claims in the accessToken. On premise ADFS
  27. userinfo = getTokenContent(accessToken);
  28. }
  29. else
  30. {
  31. // normal behaviour, getting the claims from UserInfo endpoint.
  32. userinfo = getUserInfo(accessToken);
  33. }
  34. if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack
  35. if (userinfo.metadata) userinfo = userinfo.metadata // Openshift hack
  36. if (debug) console.log('XXX: userinfo:', userinfo);
  37. var serviceData = {};
  38. serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"];
  39. serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"];
  40. serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
  41. serviceData.accessToken = accessToken;
  42. serviceData.expiresAt = expiresAt;
  43. // If on Oracle OIM email is empty or null, get info from username
  44. if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) {
  45. if (userinfo[process.env.OAUTH2_EMAIL_MAP]) {
  46. serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP];
  47. } else {
  48. serviceData.email = userinfo[process.env.OAUTH2_USERNAME_MAP];
  49. }
  50. }
  51. if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) {
  52. serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
  53. }
  54. if (accessToken) {
  55. var tokenContent = getTokenContent(accessToken);
  56. var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields);
  57. _.extend(serviceData, fields);
  58. }
  59. if (token.refresh_token)
  60. serviceData.refreshToken = token.refresh_token;
  61. if (debug) console.log('XXX: serviceData:', serviceData);
  62. var profile = {};
  63. profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
  64. profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
  65. if (propagateOidcData)
  66. {
  67. users= Meteor.users;
  68. user = users.findOne({'services.oidc.id': serviceData.id});
  69. if(user)
  70. {
  71. (!userinfo?.["wekanGroups"]?.length) ? addGroups(user, userinfo["groups"]): addGroupsWithAttributes(user, userinfo["wekanGroups"]);
  72. if(profile.email) addEmail(user, profile.email);
  73. if(profile.name) changeFullname(user, profile.name);
  74. if(profile.username) changeUsername(user, profile.username);
  75. }
  76. }
  77. if (debug) console.log('XXX: profile:', profile);
  78. return {
  79. serviceData: serviceData,
  80. options: { profile: profile }
  81. };
  82. });
  83. var userAgent = "Meteor";
  84. if (Meteor.release) {
  85. userAgent += "/" + Meteor.release;
  86. }
  87. if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) {
  88. var getToken = function (query) {
  89. var debug = process.env.DEBUG || false;
  90. var config = getConfiguration();
  91. if(config.tokenEndpoint.includes('https://')){
  92. var serverTokenEndpoint = config.tokenEndpoint;
  93. }else{
  94. var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
  95. }
  96. var requestPermissions = config.requestPermissions;
  97. var response;
  98. try {
  99. var postOptions = {
  100. headers: {
  101. Accept: 'application/json',
  102. "User-Agent": userAgent
  103. },
  104. params: {
  105. code: query.code,
  106. client_id: config.clientId,
  107. client_secret: OAuth.openSecret(config.secret),
  108. redirect_uri: OAuth._redirectUri('oidc', config),
  109. grant_type: 'authorization_code',
  110. state: query.state
  111. }
  112. };
  113. if (httpCa) {
  114. postOptions['npmRequestOptions'] = { ca: httpCa };
  115. }
  116. response = HTTP.post(serverTokenEndpoint, postOptions);
  117. } catch (err) {
  118. throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
  119. { response: err.response });
  120. }
  121. if (response.data.error) {
  122. // if the http response was a json object with an error attribute
  123. throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error);
  124. } else {
  125. if (debug) console.log('XXX: getToken response: ', response.data);
  126. return response.data;
  127. }
  128. };
  129. }
  130. if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) {
  131. var getToken = function (query) {
  132. var debug = (process.env.DEBUG === 'true' || process.env.DEBUG === true) || false;
  133. var config = getConfiguration();
  134. if(config.tokenEndpoint.includes('https://')){
  135. var serverTokenEndpoint = config.tokenEndpoint;
  136. }else{
  137. var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
  138. }
  139. var requestPermissions = config.requestPermissions;
  140. var response;
  141. // OIM needs basic Authentication token in the header - ClientID + SECRET in base64
  142. var dataToken=null;
  143. var strBasicToken=null;
  144. var strBasicToken64=null;
  145. dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET;
  146. strBasicToken = new Buffer(dataToken);
  147. strBasicToken64 = strBasicToken.toString('base64');
  148. // eslint-disable-next-line no-console
  149. if (debug) console.log('Basic Token: ', strBasicToken64);
  150. try {
  151. var postOptions = {
  152. headers: {
  153. Accept: 'application/json',
  154. "User-Agent": userAgent,
  155. "Authorization": "Basic " + strBasicToken64
  156. },
  157. params: {
  158. code: query.code,
  159. client_id: config.clientId,
  160. client_secret: OAuth.openSecret(config.secret),
  161. redirect_uri: OAuth._redirectUri('oidc', config),
  162. grant_type: 'authorization_code',
  163. state: query.state
  164. }
  165. };
  166. if (httpCa) {
  167. postOptions['npmRequestOptions'] = { ca: httpCa };
  168. }
  169. response = HTTP.post(serverTokenEndpoint, postOptions);
  170. } catch (err) {
  171. throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
  172. { response: err.response });
  173. }
  174. if (response.data.error) {
  175. // if the http response was a json object with an error attribute
  176. throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error);
  177. } else {
  178. // eslint-disable-next-line no-console
  179. if (debug) console.log('XXX: getToken response: ', response.data);
  180. return response.data;
  181. }
  182. };
  183. }
  184. var getUserInfo = function (accessToken) {
  185. var debug = process.env.DEBUG || false;
  186. var config = getConfiguration();
  187. // Some userinfo endpoints use a different base URL than the authorization or token endpoints.
  188. // This logic allows the end user to override the setting by providing the full URL to userinfo in their config.
  189. if (config.userinfoEndpoint.includes("https://")) {
  190. var serverUserinfoEndpoint = config.userinfoEndpoint;
  191. } else {
  192. var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint;
  193. }
  194. var response;
  195. try {
  196. var getOptions = {
  197. headers: {
  198. "User-Agent": userAgent,
  199. "Authorization": "Bearer " + accessToken
  200. }
  201. };
  202. if (httpCa) {
  203. getOptions['npmRequestOptions'] = { ca: httpCa };
  204. }
  205. response = HTTP.get(serverUserinfoEndpoint, getOptions);
  206. } catch (err) {
  207. throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message),
  208. {response: err.response});
  209. }
  210. if (debug) console.log('XXX: getUserInfo response: ', response.data);
  211. return response.data;
  212. };
  213. var getConfiguration = function () {
  214. var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' });
  215. if (!config) {
  216. throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
  217. }
  218. return config;
  219. };
  220. var getTokenContent = function (token) {
  221. var content = null;
  222. if (token) {
  223. try {
  224. var parts = token.split('.');
  225. var header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
  226. content = JSON.parse(Buffer.from(parts[1], 'base64').toString());
  227. var signature = Buffer.from(parts[2], 'base64');
  228. var signed = parts[0] + '.' + parts[1];
  229. } catch (err) {
  230. this.content = {
  231. exp: 0
  232. };
  233. }
  234. }
  235. return content;
  236. }
  237. Oidc.retrieveCredential = function (credentialToken, credentialSecret) {
  238. return OAuth.retrieveCredential(credentialToken, credentialSecret);
  239. };