diff --git a/Dockerfile b/Dockerfile index f5827a2f2..4de6acab1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,15 +38,15 @@ COPY plugins/arcgis/service/ ./ RUN npm run build RUN npm pack -FROM node:20.11.1 AS build-imageserviceplugin -WORKDIR /imageserviceplugin -COPY plugins/image/service/package*.json ./ -RUN npm install -COPY --from=build-service /service /imageserviceplugin/node_modules/@ngageoint/mage.service -RUN rm -rf /imageserviceplugin/node_modules/@ngageoint/mage.service/node_modules/mongoose -COPY plugins/image/service/ ./ -RUN npm run build -RUN npm pack +# FROM node:20.11.1 AS build-imageserviceplugin +# WORKDIR /imageserviceplugin +# COPY plugins/image/service/package*.json ./ +# RUN npm install +# COPY --from=build-service /service /imageserviceplugin/node_modules/@ngageoint/mage.service +# RUN rm -rf /imageserviceplugin/node_modules/@ngageoint/mage.service/node_modules/mongoose +# COPY plugins/image/service/ ./ +# RUN npm run build +# RUN npm pack FROM node:20.11.1 AS build-sftpserviceplugin WORKDIR /sftpserviceplugin @@ -74,7 +74,6 @@ COPY --from=build-arcwebplugin /arcgiswebplugin/ngageoint*.tgz /arcgiswebplugin/ COPY --from=build-arcserviceplugin /arcgisserviceplugin/ngageoint*.tgz /arcgisserviceplugin/ COPY --from=build-sftpserviceplugin /sftpserviceplugin/ngageoint*.tgz /sftpserviceplugin/ COPY --from=build-sftpwebplugin /sftpwebplugin/ngageoint*.tgz /sftpwebplugin/ -COPY --from=build-imageserviceplugin /imageserviceplugin/ngageoint*.tgz /imageserviceplugin/ WORKDIR /instance RUN ls -la ../sftpwebplugin @@ -84,7 +83,6 @@ RUN npm install ../sftpwebplugin/ngageoint-mage.sftp.web*.tgz RUN npm install ../sftpserviceplugin/ngageoint-mage.sftp.service*.tgz RUN npm install ../arcgiswebplugin/ngageoint*.tgz RUN npm install ../arcgisserviceplugin/ngageoint*.tgz -RUN npm install ../imageserviceplugin/ngageoint*.tgz ENV NODE_PATH=./node_modules diff --git a/docker/auth-idp/docker-compose.yml b/docker/auth-idp/docker-compose.yml index 63b5c6569..78e7818a1 100644 --- a/docker/auth-idp/docker-compose.yml +++ b/docker/auth-idp/docker-compose.yml @@ -26,12 +26,8 @@ services: - 389:389 - 636:636 volumes: - - type: bind - source: ./ldap/db - target: /var/lib/ldap - - type: bind - source: ./ldap/config - target: /etc/ldap/slapd.d + - ./ldapseed.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-bootstrap.ldif + command: --copy-service networks: - ldap.mage.net diff --git a/docker/auth-idp/ldap/config/.gitkeep b/docker/auth-idp/ldap/config/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/docker/auth-idp/ldap/db/.gitkeep b/docker/auth-idp/ldap/db/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/docker/auth-idp/ldapseed.ldif b/docker/auth-idp/ldapseed.ldif new file mode 100755 index 000000000..2e78ba1f7 --- /dev/null +++ b/docker/auth-idp/ldapseed.ldif @@ -0,0 +1,19 @@ +dn: ou=Field Agents,dc=wgd,dc=com +objectClass: organizationalUnit +ou: Field Agents + +dn: uid=batman,ou=Field Agents,dc=wgd,dc=com +objectClass: inetOrgPerson +uid: batman +sn: Batman +cn: Bruce Wayne +userPassword: i heart alfred +mail: Bruce.wayne@wgd.com + +dn: uid=robin,ou=Field Agents,dc=wgd,dc=com +objectClass: inetOrgPerson +uid: robin +sn: Robin +cn: Dick Grayson +userPassword: i heart batman +mail: dick.grayson@wgd.com \ No newline at end of file diff --git a/docker/auth-idp/saml/authsources.php b/docker/auth-idp/saml/authsources.php index 2b91cca01..c36b5202d 100644 --- a/docker/auth-idp/saml/authsources.php +++ b/docker/auth-idp/saml/authsources.php @@ -8,15 +8,15 @@ 'example-userpass' => array( 'exampleauth:UserPass', - 'saml.user1:user1pass' => array( + 'frodo.baggins:showmerings' => array( 'uid' => array('1'), 'eduPersonAffiliation' => array('group1'), - 'email' => 'user1@saml.mage.test', + 'email' => 'frodo.baggins@saml.mage.test', ), - 'saml.user2:user2pass' => array( + 'samwise.gamgee:bringyourgardner' => array( 'uid' => array('2'), 'eduPersonAffiliation' => array('group2'), - 'email' => 'user2@saml.mage.test', + 'email' => 'samwise.gamgee@saml.mage.test', ), ), diff --git a/docs/admin/auth.md b/docs/admin/auth.md index 527685372..9e7026a94 100644 --- a/docs/admin/auth.md +++ b/docs/admin/auth.md @@ -46,7 +46,11 @@ Click the _Login_ link on the left pane. The root user name and password for the LDAP server are `cn=admin,dc=wgd,dc=com` and `i found something`, respectively. -You can then use the phpLDAPAdmin UI to setup a simple group structure. +The docker/docker-compose.yml file starts up the mage-idp-ldap container with the ldapseed.ldiff file. +This file will seed the ldap server with a batman and robin user under the Field Agents org. Below are steps to do the same +using the phpLDAPAdmin UI should any more users need to be generated. + +Using the phpLDAPAdmin UI to setup a simple group structure. 1. Click the _dc=wgd,dc=com_ root node in the tree view on the left of the page. 1. In the main pane, click _Create a child entry_. 1. Select the _Generic: Posix Group_ template. @@ -79,9 +83,7 @@ authentication in MAGE. This assumes you're running a MAGE server on http://localhost:4242. 1. Open the MAGE web app in your browser. 1. Click the gear icon in the top right to load the _Admin_ page. -1. Click the _Settings_ tab in the vertical tab strip on the left. -1. The _Authentication_ tab in the main pane should already be active. Click - the tab if not. +1. Click the _Security_ tab in the vertical tab strip on the left. 1. Click the _New Authentication_ button. 1. Enter a title for the authentication IDP, e.g. `Test LDAP`. 1. Click the _Next_ button. @@ -126,3 +128,50 @@ http://localhost:4242. 1. The app may prompt for a device UID if your settings dictate. Enter the device UID. 1. You are now authenticated with your LDAP account. + +## SAML +You can setup MAGE to authenticate users with an SAML server. For development +testing, the [`auth-idp`](../docker/auth-idp/docker-compose.yml) Compose file +uses the [kristophjunge/test-saml-idp](https://github.com/kristophjunge/docker-test-saml-idp) +Start the `mage-idp-saml` SAML +service with the following commands. +```bash +cd docker/auth-idp +docker compose up -d mage-idp-saml +``` + +The docker compose file is set to seed a few users with the .saml/authsources.php file. This +file is mounted under volumes in the docker compose. Once it is spun up, you are ready to +configure your saml authentication provider. + +1. Open the MAGE web app in your browser. +1. Click the gear icon in the top right to load the _Admin_ page. +1. Click the _Security_ tab in the vertical tab strip on the left. +1. Click the _New Authentication_ button. +1. Enter a title for the authentication IDP, e.g. `Test SAML`. +1. Click the _Next_ button. +1. Fill in the _Settings_ fields as follows. + | | | + | ---: | ---| + | **_Idepntity Provider (idP)_** | + | _Entry Point_ | `http://localhost:8080/simplesaml/saml2/idp/SSOService.php` | + | _Issuer_ | `http://localhost:4242` | + | _Redirect Host_ | `http://localhost:4242/auth/saml/callback` | + | **_Security_** | + | _idP Public Signing Certificate_ | navigate to http://localhost:8080/simplesaml/saml2/idp/metadata.php. look for the tag and copy everything in that tag | + | **_Validation_** | + | **_Issuer_** | + | _idP Issuer_ | `http://localhost:8080/simplesaml/saml2/idp/metadata.php` | + | **_Logout_** | + | _logout URL_ | `http://localhost:8080/simplesaml/saml2/idp/SingleLogoutService.php` | +1. Click the _Next_ button. +1. Adjust the color settings to your preference. +1. Click the _Next_ button. +1. Review the settings and click the _Save_ button. +1. Open a new private browser tab or window and load your MAGE server web app. +1. The sign-in page should display a button labeled _Continue with SAML_ +1. Clicking this button will take you to a simple login page where the saml server is running. +1. for Username enter `saml.user1` +1. for Pasword enter `user2pass` +1. Depending on how the login was configured, you will either be redirected through to the application, or + a user will have been created which will require approval from an admin. \ No newline at end of file diff --git a/localseed.ldif b/localseed.ldif new file mode 100755 index 000000000..16b272502 --- /dev/null +++ b/localseed.ldif @@ -0,0 +1,19 @@ +dn: ou=users,dc=shire,dc=com +objectClass: organizationalUnit +ou: users + +dn: uid=frodob,ou=users,dc=shire,dc=com +objectClass: inetOrgPerson +uid: frodob +sn: Baggins +cn: Frodo Baggins +userPassword: P@$$Word1234!@#$ +mail: frodo.baggins@shire.com + +dn: uid=sammyg,ou=users,dc=shire,dc=com +objectClass: inetOrgPerson +uid: sammyg +sn: Gamgee +cn: Samwise Gamgee +userPassword: P@$$Word!@#$1234 +mail: damwise.gamgee@shire.com \ No newline at end of file diff --git a/service/src/authentication/ldap.js b/service/src/authentication/ldap.js index 719808363..f00fe4133 100644 --- a/service/src/authentication/ldap.js +++ b/service/src/authentication/ldap.js @@ -23,7 +23,9 @@ function configure(strategy) { groupSearchScope: strategy.settings.groupSearchScope, bindProperty: strategy.settings.bindProperty, groupDnProperty: strategy.settings.groupDnProperty - } + }, + usernameField: 'username', + passwordField: 'password' }, function (profile, done) { const username = profile[strategy.settings.profile.id ]; @@ -103,43 +105,42 @@ function initialize(strategy) { invalidCredentials: `Invalid ${strategy.title} username/password.` }; - app.post(`/auth/${strategy.name}/signin`, - function authenticate(req, res, next) { - passport.authenticate(strategy.name, authenticationOptions, function (err, user, info = {}) { - if (err) return next(err); - - if (!user) { - return res.status(401).send(info.message); - } - - if (!user.active) { - return res.status(info.status || 401).send('User account is not approved, please contact your MAGE administrator to approve your account.'); - } - - if (!user.enabled) { - log.warn('Failed user login attempt: User ' + user.username + ' account is disabled.'); - return res.status(401).send('Your account has been disabled, please contact a MAGE administrator for assistance.') - } - - if (!user.authentication.authenticationConfigurationId) { - log.warn('Failed user login attempt: ' + user.authentication.type + ' is not configured'); - return res.status(401).send(user.authentication.type + ' authentication is not configured, please contact a MAGE administrator for assistance.') - } - - if (!user.authentication.authenticationConfiguration.enabled) { - log.warn('Failed user login attempt: Authentication ' + user.authentication.authenticationConfiguration.title + ' is disabled.'); - return res.status(401).send(user.authentication.authenticationConfiguration.title + ' authentication is disabled, please contact a MAGE administrator for assistance.') - } - - tokenService.generateToken(user._id.toString(), TokenAssertion.Authorized, 60 * 5) - .then(token => { - res.json({ - user: userTransformer.transform(req.user, { path: req.getRoot() }), - token: token - }); - }).catch(err => next(err)); - })(req, res, next); - } + app.post(`/auth/${strategy.name}/signin`, function authenticate(req, res, next) { + passport.authenticate(strategy.name, authenticationOptions, function (err, user, info = {}) { + if (err) return next(err); + + if (!user) { + return res.status(401).send(info.message); + } + + if (!user.active) { + return res.status(info.status || 401).send('User account is not approved, please contact your MAGE administrator to approve your account.'); + } + + if (!user.enabled) { + log.warn('Failed user login attempt: User ' + user.username + ' account is disabled.'); + return res.status(401).send('Your account has been disabled, please contact a MAGE administrator for assistance.') + } + + if (!user.authentication.authenticationConfigurationId) { + log.warn('Failed user login attempt: ' + user.authentication.type + ' is not configured'); + return res.status(401).send(user.authentication.type + ' authentication is not configured, please contact a MAGE administrator for assistance.') + } + + if (!user.authentication.authenticationConfiguration.enabled) { + log.warn('Failed user login attempt: Authentication ' + user.authentication.authenticationConfiguration.title + ' is disabled.'); + return res.status(401).send(user.authentication.authenticationConfiguration.title + ' authentication is disabled, please contact a MAGE administrator for assistance.') + } + + tokenService.generateToken(user._id.toString(), TokenAssertion.Authorized, 60 * 5) + .then(token => { + res.json({ + user: userTransformer.transform(req.user, { path: req.getRoot() }), + token: token + }); + }).catch(err => next(err)); + })(req, res, next); + } ); }; diff --git a/web-app/src/app/user/user.service.ts b/web-app/src/app/user/user.service.ts index f46feb57d..63ca64194 100644 --- a/web-app/src/app/user/user.service.ts +++ b/web-app/src/app/user/user.service.ts @@ -69,7 +69,7 @@ export class UserService { } ldapSignin(username: string, password: string): Observable { - return this.httpClient.post('/api/ldap/signin', { + return this.httpClient.post('/auth/ldap/signin', { username, password, appVersion: 'Web Client'