Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile.ldap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM osixia/openldap:1.5.0
COPY localseed.ldif /container/service/slapd/assets/config/bootstrap/ldif/50-seed.ldif
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ services:
# NOTE: default INSECURE salt value, recommend generate new UUID before deployment, **NOT** after deployment
SFTP_PLUGIN_CONFIG_SALT: "A0E6D3B4-25BD-4DD6-BBC9-B367931966AB"

ldap:
build:
context: .
dockerfile: Dockerfile.ldap
container_name: openldap
environment:
- LDAP_ORGANISATION=HobbitsInc
- LDAP_DOMAIN=shire.com
- LDAP_ADMIN_PASSWORD=admin
ports:
- "389:389"
- "636:636"
volumes:
- ./localseed.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-seed.ldif
# Uncomment the following block to enable the TLS reverse proxy. You will
# also need to generate the key and certificate as the README describes.
# mage-web-proxy:
Expand Down
8 changes: 2 additions & 6 deletions docker/auth-idp/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Empty file.
Empty file removed docker/auth-idp/ldap/db/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions docker/auth-idp/ldapseed.ldif
Original file line number Diff line number Diff line change
@@ -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: [email protected]

dn: uid=robin,ou=Field Agents,dc=wgd,dc=com
objectClass: inetOrgPerson
uid: robin
sn: Robin
cn: Dick Grayson
userPassword: i heart batman
mail: [email protected]
8 changes: 4 additions & 4 deletions docker/auth-idp/saml/authsources.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
),
),

Expand Down
57 changes: 53 additions & 4 deletions docs/admin/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <ds:X509Certificate> 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.
19 changes: 19 additions & 0 deletions localseed.ldif
Original file line number Diff line number Diff line change
@@ -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: [email protected]

dn: uid=sammyg,ou=users,dc=shire,dc=com
objectClass: inetOrgPerson
uid: sammyg
sn: Gamgee
cn: Samwise Gamgee
userPassword: P@$$Word!@#$1234
mail: [email protected]
77 changes: 39 additions & 38 deletions service/src/authentication/ldap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ];
Expand Down Expand Up @@ -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);
}
);
};

Expand Down
2 changes: 1 addition & 1 deletion web-app/src/app/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class UserService {
}

ldapSignin(username: string, password: string): Observable<any> {
return this.httpClient.post<any>('/api/ldap/signin', {
return this.httpClient.post<any>('/auth/ldap/signin', {
username,
password,
appVersion: 'Web Client'
Expand Down