oaeproject/Hilary

View on GitHub
packages/oae-principals/lib/rest.user.js

Summary

Maintainability
F
5 days
Test Coverage
A
97%
/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

import { format } from 'node:util';
import process from 'node:process';
import { setUpConfig } from 'oae-config';

import * as AuthenticationAPI from 'oae-authentication';
import * as OAE from 'oae-util/lib/oae.js';
import * as OaeUtil from 'oae-util/lib/util.js';

import { Recaptcha } from 'recaptcha';
import { AuthenticationConstants } from 'oae-authentication/lib/constants.js';
import { LoginId } from 'oae-authentication/lib/model.js';
import { prop, is, pipe } from 'ramda';
import PrincipalsAPI from './api.js';

const PrincipalsConfig = setUpConfig('oae-principals');

/**
 * @REST getUserTermsAndConditions
 *
 * Get the Terms and Conditions
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /user/termsAndConditions
 * @QueryParam  {string}                [locale]        The locale in which the Terms and Conditions should be retrieved. Defaults to the default Terms and Conditions
 * @Return      {TermsAndConditions}                    The Terms and Conditions
 * @HttpResponse                        200             Terms and conditions available
 */
OAE.tenantRouter.on('get', '/api/user/termsAndConditions', (request, response) => {
  const termsAndConditions = PrincipalsAPI.getTermsAndConditions(request.ctx, request.query.locale);
  response.status(200).send(termsAndConditions);
});

/**
 * @REST postUserUserIdTermsAndConditions
 *
 * Accept the Terms and Conditions
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/{userId}/termsAndConditions
 * @PathParam   {string}        userId                  The id of the user accepting the Terms and Conditions
 * @Return      {BasicUser}                             The updated user
 * @HttpResponse                200                     Terms and conditions acceptance noted
 * @HttpResponse                400                     Invalid userId passed in
 * @HttpResponse                400                     The Terms and Conditions are not enabled, there is no need to accept them
 * @HttpResponse                401                     Only logged in users can accept the Terms and Conditions
 * @HttpResponse                401                     You are not authorized to accept the Terms and Conditions on behalf of this user
 */
OAE.tenantRouter.on('post', '/api/user/:userId/termsAndConditions', (request, response) => {
  PrincipalsAPI.acceptTermsAndConditions(request.ctx, request.params.userId, (error, user) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(user);
  });
});

/**
 * @REST postUserCreateGlobalAdminUser
 *
 * Create a new global administrator with local authentication
 *
 * @Server      admin
 * @Method      POST
 * @Path        /user/createGlobalAdminUser
 * @FormParam   {string}        displayName             The display name for the global administrator
 * @FormParam   {string}        password                The password for the global administrator
 * @FormParam   {string}        username                The unique username for the global administrator
 * @FormParam   {string}        email                   The email address for the global administrator
 * @FormParam   {string}        [emailPreference]       The email preference for the global administrator   [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                The locale for the global administrator
 * @FormParam   {string}        [publicAlias]           The name to show when the global administrator is inaccessible to a user
 * @Return      {BasicUser}                             The created global administrator
 * @HttpResponse                201                     Global administrator created
 * @HttpResponse                400                     A display name can be at most 1000 characters long
 * @HttpResponse                400                     A user with username ... already exists
 * @HttpResponse                400                     You must provide a display name
 * @HttpResponse                400                     You must provide a password
 * @HttpResponse                400                     You must provide a username
 * @HttpResponse                401                     You do not have sufficient rights to make someone an admin
 * @HttpResponse                401                     You must be a global administrator to create a global administrator user
 */
OAE.globalAdminRouter.on('post', '/api/user/createGlobalAdminUser', (request, response) => {
  const options = _getOptionalProfileParameters(request.body);

  // Create the user as global admin
  AuthenticationAPI.getOrCreateGlobalAdminUser(
    request.ctx,
    request.body.username,
    request.body.password,
    request.body.displayName,
    options,
    (error, user, loginId, created) => {
      if (error) {
        return response.status(error.code).send(error.msg);
      }

      if (!created) {
        return response.status(400).send(format('A user with username "%s" already exists', request.body.username));
      }

      return response.status(201).send(user);
    }
  );
});

/**
 * @REST postUserTenantAliasCreateTenantAdminUser
 *
 * Create a new tenant administrator with local authentication
 *
 * @Server      admin
 * @Method      POST
 * @Path        /user/{tenantAlias}/createTenantAdminUser
 * @PathParam   {string}        tenantAlias             The alias of the tenant for which to create a tenant administrator
 * @FormParam   {string}        displayName             The display name for the tenant administrator
 * @FormParam   {string}        password                The password for the tenant administrator
 * @FormParam   {string}        username                The unique username for the tenant administrator
 * @FormParam   {string}        email                   The email address for the tenant administrator
 * @FormParam   {boolean}       [acceptedTC]            Whether or not the tenant administrator has accepted the Terms and Conditions
 * @FormParam   {string}        [emailPreference]       The email preference for the tenant administrator   [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                The locale for the tenant administrator
 * @FormParam   {string}        [publicAlias]           The name to show when the tenant administrator is inaccessible to a user
 * @Return      {BasicUser}                             The created tenant administrator
 * @HttpResponse                201                     Tenant administrator created
 * @HttpResponse                400                     A display name can be at most 1000 characters long
 * @HttpResponse                400                     A display name must be provided
 * @HttpResponse                400                     The specified email preference is invalid
 * @HttpResponse                400                     The specified visibility setting is unknown
 * @HttpResponse                401                     Only administrators can create new tenant administrators
 * @HttpResponse                401                     You do not have sufficient rights to make someone an admin
 * @HttpResponse                404                     A non-existing tenant was specified as the target for this user
 */

/**
 * @REST postUserCreateTenantAdminUser
 *
 * Create a new tenant administrator with local authentication
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/createTenantAdminUser
 * @FormParam   {string}        displayName             The display name for the tenant administrator
 * @FormParam   {string}        password                The password for the tenant administrator
 * @FormParam   {string}        username                The unique username for the tenant administrator
 * @FormParam   {string}        email                   The email address for the global administrator
 * @FormParam   {boolean}       [acceptedTC]            Whether or not the tenant administrator has accepted the Terms and Conditions
 * @FormParam   {string}        [emailPreference]       The email preference for the tenant administrator   [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                The locale for the tenant administrator
 * @FormParam   {string}        [publicAlias]           The name to show when the tenant administrator is inaccessible to a user
 * @Return      {BasicUser}                             The created tenant administrator
 * @HttpResponse                201                     Tenant administrator created
 * @HttpResponse                400                     A display name can be at most 1000 characters long
 * @HttpResponse                400                     A display name must be provided
 * @HttpResponse                400                     The specified email preference is invalid
 * @HttpResponse                400                     The specified visibility setting is unknown
 * @HttpResponse                401                     Only administrators can create new tenant administrators
 * @HttpResponse                401                     You do not have sufficient rights to make someone an admin
 */
const _handleCreateTenantAdminUser = function (request, response) {
  const { ctx } = request;
  const tenantAlias = request.params.tenantAlias || ctx.tenant().alias;
  const loginId = new LoginId(tenantAlias, AuthenticationConstants.providers.LOCAL, request.body.username, {
    password: request.body.password
  });
  const options = _getOptionalProfileParameters(request.body);

  // Create the user as a tenant administrator
  AuthenticationAPI.createTenantAdminUser(ctx, loginId, request.body.displayName, options, (error, user) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(201).send(user);
  });
};

OAE.globalAdminRouter.on('post', '/api/user/:tenantAlias/createTenantAdminUser', _handleCreateTenantAdminUser);
OAE.tenantRouter.on('post', '/api/user/createTenantAdminUser', _handleCreateTenantAdminUser);

/**
 * @REST postUserTenantAliasCreate
 *
 * Create a new user with local authentication
 *
 * @Server      admin
 * @Method      POST
 * @Path        /user/{tenantAlias}/create
 * @PathParam   {string}        tenantAlias             The alias of the tenant for which to create a user
 * @FormParam   {string}        displayName             The display name for the user
 * @FormParam   {string}        password                The password for the user
 * @FormParam   {string}        username                The unique username for the user
 * @FormParam   {string}        email                   The email address for the user
 * @FormParam   {boolean}       [acceptedTC]            Whether or not the user has accepted the Terms and Conditions
 * @FormParam   {string}        [emailPreference]       The email preference for the user       [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                The locale for the user
 * @FormParam   {string}        [publicAlias]           The name to show when the user is inaccessible to a user
 * @FormParam   {string}        [visibility]            The visibility of the user              [loggedin,private,public]
 * @Return      {BasicUser}                             The created user
 * @HttpResponse                201                     User created
 * @HttpResponse                400                     A display name must be provided
 * @HttpResponse                400                     A display name can be at most 1000 characters long
 * @HttpResponse                400                     The specified email preference is invalid
 * @HttpResponse                400                     The specified visibility setting is unknown
 * @HttpResponse                401                     Only global administrators may create a user on the global admin tenant
 * @HttpResponse                401                     Only global administrators may create users on a tenant that is not the current
 * @HttpResponse                404                     A non-existing tenant was specified as the target for this user
 */
OAE.globalAdminRouter.on('post', '/api/user/:tenantAlias/create', (request, response) => {
  const loginId = new LoginId(
    request.params.tenantAlias,
    AuthenticationConstants.providers.LOCAL,
    request.body.username,
    {
      password: request.body.password
    }
  );
  const options = {
    visibility: request.body.visibility,
    email: request.body.email,
    emailPreference: request.body.emailPreference,
    locale: request.body.locale || request.ctx.locale(),
    timezone: request.body.timezone,
    publicAlias: request.body.publicAlias,
    acceptedTC: request.body.acceptedTC === 'true'
  };

  AuthenticationAPI.createUser(request.ctx, loginId, request.body.displayName, options, (error, newUser) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(201).send(newUser);
  });
});

/**
 * @REST postUserCreate
 *
 * Create a new user with local authentication
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/create
 * @FormParam   {string}        displayName             The display name for the user
 * @FormParam   {string}        password                The password for the user
 * @FormParam   {string}        username                The unique username for the user
 * @FormParam   {string}        email                   The email address for the user
 * @FormParam   {boolean}       [acceptedTC]            Whether or not the user has accepted the Terms and Conditions
 * @FormParam   {string}        [emailPreference]       The email preference for the user       [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                The locale for the user
 * @FormParam   {string}        [publicAplias]           The name to show when the user is inaccessible to a user
 * @FormParam   {string}        [visibility]            The visibility of the user              [loggedin,private,public]
 * @Return      {BasicUser}                             The created user
 * @HttpResponse                201                     User created
 * @HttpResponse                400                     A display name must be provided
 * @HttpResponse                400                     A display name can be at most 1000 characters long
 * @HttpResponse                400                     Invalid reCaptcha token
 * @HttpResponse                400                     The specified email preference is invalid
 * @HttpResponse                400                     The specified visibility setting is unknown
 * @HttpResponse                400                     The specified username already exists
 * @HttpResponse                400                     You need to accept the Terms and Conditions
 * @HttpResponse                401                     Unauthorized
 */
OAE.tenantRouter.on('post', '/api/user/create', (request, response) => {
  const { ctx } = request;
  const tenant = ctx.tenant();
  const user = ctx.user();
  const options = _getOptionalProfileParameters(request.body);

  // We enforce an email address, the API will take care of validating the actual value
  if (!options.email) {
    return response.status(400).send('A valid email address is required when creating a local account');
  }

  options.invitationToken = request.body.invitationToken;

  const getErrorCode = (error) => {
    if (is(Error, error)) {
      const parseError = pipe(prop('message'), JSON.parse);
      return {
        responseErrorCode: pipe(parseError, prop('code'))(error),
        responseErrorMessage: pipe(parseError, prop('msg'))(error)
      };
    }

    return { responseErrorCode: error.code, responseErrorMessage: error.msg };
  };

  /*!
   * Create a local user account
   */
  const createUser = function () {
    AuthenticationAPI.getOrCreateUser(
      ctx,
      AuthenticationConstants.providers.LOCAL,
      request.body.username,
      { password: request.body.password },
      request.body.displayName,
      options,
      (error, newUser, loginId, created) => {
        if (error) {
          /**
           * Again, we need to deal with both Error and Object instances
           */
          const { responseErrorCode, responseErrorMessage } = getErrorCode(error);
          return response.status(responseErrorCode).send(responseErrorMessage);
        }

        if (!created) {
          return response.status(400).send('A user with that username already exists').end();
        }

        return response.status(201).send(newUser);
      }
    );
  };

  if (user) {
    if (user.isAdmin(tenant.alias)) {
      // If the current user is an admin, the reCaptcha verification can be skipped
      return createUser();
    }

    // Non-admin users cannot create accounts
    return response.status(401).end();
  }

  if (options.invitationToken) {
    // Bypass recaptcha if an invitation token is provided. The process of creating a user will
    // fail if the invitation token is not valid
    return createUser();
  }

  // Check if the Terms and Conditions has been agreed to (if applicable)
  const needsTermsAndConditionsAgreement = PrincipalsConfig.getValue(tenant.alias, 'termsAndConditions', 'enabled');
  if (needsTermsAndConditionsAgreement && options.acceptedTC !== true) {
    return response.status(400).send('You need to accept the Terms and Conditions');
  }

  // Check if we need to validate with reCaptcha
  const needsReCaptcha = PrincipalsConfig.getValue(tenant.alias, 'recaptcha', 'enabled');
  if (!needsReCaptcha) {
    return createUser();
  }

  // An anonymous user, do the recaptcha check
  const recaptchaData = {
    remoteip: request.connection.remoteAddress,
    challenge: request.body.recaptchaChallenge,
    response: request.body.recaptchaResponse
  };
  const recaptchaPublicKey = PrincipalsConfig.getValue(tenant.alias, 'recaptcha', 'publicKey');
  const recaptchaPrivateKey = PrincipalsConfig.getValue(tenant.alias, 'recaptcha', 'privateKey');
  const recaptcha = new Recaptcha(recaptchaPublicKey, recaptchaPrivateKey, recaptchaData);
  // eslint-disable-next-line no-unused-vars
  recaptcha.verify((success, error) => {
    if (success) {
      return createUser();
    }

    return response.status(400).send('Invalid reCaptcha token');
  });
});

/**
 * Convenience function to handle deleting a user
 *
 * @param  {Request}    req     The express request
 * @param  {Response}   res     The express response
 * @api private
 */
const _handleDeleteUser = function (request, response) {
  PrincipalsAPI.deleteUser(request.ctx, request.params.userId, (error) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).end();
  });
};

/**
 * @REST deleteUserUserId
 *
 * @Server      admin,tenant
 * @Method      DELETE
 * @Path        /user/{userId}
 * @PathParam   {string}        userId      The id of the user to delete
 * @HttpResponse                200         The user was successfully deleted
 * @HttpResponse                400         An invalid user id was specified
 * @HttpResponse                401         You do not have access to delete this user
 * @HttpResponse                404         The user did not exist
 */
OAE.tenantRouter.on('delete', '/api/user/:userId', _handleDeleteUser);
OAE.globalAdminRouter.on('delete', '/api/user/:userId', _handleDeleteUser);

/**
 * Convenience function to handle restoring a user
 *
 * @param  {Request}    req     The express request
 * @param  {Response}   res     The express response
 * @api private
 */
const _handleRestoreUser = function (request, response) {
  PrincipalsAPI.restoreUser(request.ctx, request.params.userId, (error) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).end();
  });
};

/**
 * @REST restoreUserUserId
 *
 * @Server      admin,tenant
 * @Method      POST
 * @Path        /user/{userId}
 * @PathParam   {string}        userId      The id of the user to restore
 * @HttpResponse                200         The user was successfully restored
 * @HttpResponse                400         An invalid user id was specified
 * @HttpResponse                401         You do not have access to restore this user
 * @HttpResponse                404         The user did not exist
 */
OAE.tenantRouter.on('post', '/api/user/:userId/restore', _handleRestoreUser);
OAE.globalAdminRouter.on('post', '/api/user/:userId/restore', _handleRestoreUser);

/**
 * @REST getMe
 *
 * Get the me feed for the current user
 *
 * @Server      admin,tenant
 * @Method      GET
 * @Path        /me
 * @Return      {Me}            The me feed for the current user
 * @HttpResponse        200     Me data available
 * @HttpResponse        401     You need to be authenticated to retrieve your user information
 */
const _handleGetMe = function (request, response) {
  PrincipalsAPI.getMe(request.ctx, (error, meData) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(meData);
  });
};

OAE.globalAdminRouter.on('get', '/api/me', _handleGetMe);
OAE.tenantRouter.on('get', '/api/me', _handleGetMe);

/**
 * @REST postUserImport
 *
 * Import users using a CSV file
 *
 * @Server      admin
 * @Method      POST
 * @Path        /user/import
 * @FormParam   {string}            authenticationStrategy  The authentication strategy with which the provided external ids should be associated        [cas,facebook,google,ldap,local,oauth,shibboleth,twitter]
 * @FormParam   {File}              file                    The CSV file to import
 * @FormParam   {string}            tenantAlias             The alias of the tenant for which the users should imported
 * @FormParam   {boolean}           [forceProfileUpdate]    Whether or not the user information should be updated, even when other user information is already present
 * @HttpResponse                    200                     File accepted for processing
 * @HttpResponse                    400                     A CSV file must be provided
 * @HttpResponse                    400                     A valid authentication strategy must be provided
 * @HttpResponse                    400                     An existing tenant alias must be provided
 * @HttpResponse                    401                     Only authorized admins can import users
 */

/**
 * @REST postUserImportTenant
 *
 * Import users using a CSV file
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/import
 * @FormParam   {string}            authenticationStrategy  The authentication strategy with which the provided external ids should be associated        [cas,facebook,google,ldap,local,oauth,shibboleth,signed,twitter]
 * @FormParam   {File}              file                    The CSV file to import
 * @FormParam   {boolean}           [forceProfileUpdate]    Whether or not the user information should be updated, even when other user information is already present
 * @HttpResponse                    200                     File accepted for processing
 * @HttpResponse                    400                     A CSV file must be provided
 * @HttpResponse                    400                     A valid authentication strategy must be provided
 * @HttpResponse                    400                     An existing tenant alias must be provided
 * @HttpResponse                    401                     Only authorized admins can import users
 */
const _handleImportUsers = function (request, response) {
  const forceProfileUpdate = request.body.forceProfileUpdate === 'true';
  PrincipalsAPI.importUsers(
    request.ctx,
    request.body.tenantAlias,
    request.files.file,
    request.body.authenticationStrategy,
    forceProfileUpdate,
    (error) => {
      if (error) {
        return response.status(error.code).send(error.msg);
      }

      // Set the response type to text/plain, as the UI uses an iFrame upload mechanism to support IE9
      // file uploads. If the response type is not set to text/plain, IE9 will try to download the response
      response.set('Content-Type', 'text/plain');
      return response.status(200).end();
    }
  );
};

OAE.globalAdminRouter.on('post', '/api/user/import', _handleImportUsers);
OAE.tenantRouter.on('post', '/api/user/import', _handleImportUsers);

/**
 * @REST postUserIdAdmin
 *
 * Update the tenant administrator status for a user
 *
 * @Server      admin,tenant
 * @Method      POST
 * @Path        /user/{userId}/admin
 * @PathParam   {string}        userId                      The id of the user to update the tenant administrator status for
 * @FormParam   {boolean}       admin                       Whether or not the user should become a tenant administrator
 * @Return      {void}
 * @HttpResponse                200                         User status updated
 * @HttpResponse                400                         Must provide a user id
 * @HttpResponse                400                         The provided principalId is not a user
 * @HttpResponse                400                         The provided userId is not a user identifier
 * @HttpResponse                401                         You do not have sufficient rights to make someone an admin
 */
const _handleSetTenantAdmin = function (request, response) {
  PrincipalsAPI.setTenantAdmin(request.ctx, request.params.userId, request.body.admin, (error) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).end();
  });
};

OAE.globalAdminRouter.on('post', '/api/user/:userId/admin', _handleSetTenantAdmin);
OAE.tenantRouter.on('post', '/api/user/:userId/admin', _handleSetTenantAdmin);

/**
 * @REST postUserId
 *
 * Update a user
 *
 * @Server      admin,tenant
 * @Method      POST
 * @Path        /user/{userId}
 * @PathParam   {string}        userId                      The id of the user to update
 * @FormParam   {string}        [email]                     The updated email for the user
 * @FormParam   {string}        [emailPreference]           The updated email preference for the user      [daily,immediate,weekly]
 * @FormParam   {string}        [locale]                    The updated locale for the user
 * @FormParam   {string}        [publicAlias]               The updated name to show when the user is inaccessible to a user
 * @FormParam   {string}        [visibility]                The updated visibility of the user            [loggedin,private,public]
 * @Return      {BasicUser}                                 The updated user
 * @HttpResponse                200                         User updated
 * @HttpResponse                400                         A display name cannot be empty
 * @HttpResponse                400                         A display name can be at most 1000 characters long
 * @HttpResponse                400                         A valid user id must be provided
 * @HttpResponse                400                         An invalid visibility option has been specified
 * @HttpResponse                400                         At least one basic profile field should be specified
 * @HttpResponse                400                         Restricted property was attempted to be set
 * @HttpResponse                400                         The specified emailPreference is invalid
 * @HttpResponse                401                         You are not authorized to update this user's profile
 * @HttpResponse                404                         The specified user could not be found
 */
const _handleUpdateUser = function (request, response) {
  PrincipalsAPI.updateUser(request.ctx, request.params.userId, request.body, (error, user) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(user);
  });
};

OAE.globalAdminRouter.on('post', '/api/user/:userId', _handleUpdateUser);
OAE.tenantRouter.on('post', '/api/user/:userId', _handleUpdateUser);

/**
 * @REST getUserId
 *
 * Get a full user profile
 *
 * @Server      admin,tenant
 * @Method      GET
 * @Path        /user/{userId}
 * @PathParam   {string}        userId                      The id of the user to get
 * @Return      {User}                                      Full user profile
 * @HttpResponse                200                         User profile available
 * @HttpResponse                400                         Must provide a user id
 * @HttpResponse                400                         The provided userId is not a user identifier
 * @HttpResponse                404                         The specified user could not be found
 */
const _handleGetFullProfile = function (request, response) {
  PrincipalsAPI.getFullUserProfile(request.ctx, request.params.userId, (error, user) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(user);
  });
};

OAE.globalAdminRouter.on('get', '/api/user/:userId', _handleGetFullProfile);
OAE.tenantRouter.on('get', '/api/user/:userId', _handleGetFullProfile);

/**
 * @REST getUserIdMemberships
 *
 * Get the group memberships of a principal
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /user/{userId}/memberships
 * @PathParam   {string}        userId                      The id of the principal for which to get the group memberships
 * @QueryParam  {number}        [limit]                     The maximum number of results to return. Default: 10
 * @QueryParam  {string}        [start]                     The paging token from which to start fetching group memberships
 * @Return      {MembershipsResponse}                       The principal's group memberships, either directly or indirectly
 * @HttpResponse                200                         Group memberships available
 * @HttpResponse                400                         Must specify a valid principalId
 * @HttpResponse                401                         You do not have access to this memberships library
 * @HttpResponse                404                         The specified user could not be found
 */
OAE.tenantRouter.on('get', '/api/user/:userId/memberships', (request, response) => {
  const limit = OaeUtil.getNumberParam(request.query.limit, 10, 1, 25);
  PrincipalsAPI.getMembershipsLibrary(
    request.ctx,
    request.params.userId,
    request.query.start,
    limit,
    (error, memberships, nextToken) => {
      if (error) {
        return response.status(error.code).send(error.msg);
      }

      return response.status(200).send({ results: memberships, nextToken });
    }
  );
});

/**
 * @REST postUserIdPicture
 *
 * Store the large picture for a user
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/{userId}/picture
 * @PathParam   {string}        userId                      The id of the user to store the large picture for
 * @FormParam   {File}          file                        Image that should be stored as the large user picture
 * @Return      {BasicUser}                                 The updated user
 * @HttpResponse                200                         Picture updated
 * @HttpResponse                400                         A file must be provided
 * @HttpResponse                400                         A principal ID must be provided
 * @HttpResponse                400                         Only images are accepted files
 * @HttpResponse                400                         The size of a picture has an upper limit of 10MB.
 * @HttpResponse                401                         You have to be logged in to be able to update a picture
 * @HttpResponse                404                         The specified user could not be found
 */
OAE.tenantRouter.on('post', '/api/user/:userId/picture', (request, response) => {
  request.files = request.files || {};
  PrincipalsAPI.storePicture(request.ctx, request.params.userId, request.files.file, (error, principal) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    // Set the response type to text/plain, as the UI uses an iFrame upload mechanism to support IE9
    // file uploads. If the response type is not set to text/plain, IE9 will try to download the response.
    response.set('Content-Type', 'text/plain');
    return response.status(200).send(principal);
  });
});

/**
 * @REST postUserEmailVerify
 *
 * Verify an email token
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /user/{userId}/email/verify
 * @PathParam   {string}        userId                      The id of the user to verify the email address for
 * @FormParam   {string}        token                       The token with which to verify the email address
 * @HttpResponse                200                         The token was valid and the email address for the user that was associated with it is verified
 * @HttpResponse                400                         A token was not specified
 * @HttpResponse                404                         The token was not associated with a user
 */
OAE.tenantRouter.on('post', '/api/user/:userId/email/verify', (request, response) => {
  PrincipalsAPI.verifyEmail(request.ctx, request.params.userId, request.body.token, (error, user) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(user);
  });
});

/**
 * @REST postUserIdEmailResend
 *
 * Send a new email token to a user
 *
 * @Server      admin,tenant
 * @Method      POST
 * @Path        /user/{userId}/email/resend
 * @PathParam   {string}        userId                      The id of the user to who a new email token should be sent
 * @HttpResponse                200                         The token was sent
 * @HttpResponse                400                         Invalid user id or the email address is already verified
 * @HttpResponse                401                         You do not have sufficient rights to resend a token for a user
 */
const _handleResendEmailToken = function (request, response) {
  PrincipalsAPI.resendEmailToken(request.ctx, request.params.userId, (error) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    response.status(200).end();
  });
};

OAE.globalAdminRouter.on('post', '/api/user/:userId/email/resend', _handleResendEmailToken);
OAE.tenantRouter.on('post', '/api/user/:userId/email/resend', _handleResendEmailToken);

/**
 * @REST getUserIdEmailToken
 *
 * Check whether a user has a pending email token
 *
 * @Server      admin,tenant
 * @Method      POST
 * @Path        /user/{userId}/email/token
 * @PathParam   {string}        userId                      The id of the user to for which to check whether they have a pending email token
 * @HttpResponse                200                         If there is a pending token, the address for which it is valid is returned. Otherwise, `null` is returned in its place
 * @HttpResponse                400                         Invalid user id
 * @HttpResponse                401                         You do not have sufficient rights to check whether the user has a pending token
 */
const _getEmailToken = function (request, response) {
  PrincipalsAPI.getEmailToken(request.ctx, request.params.userId, (error, email) => {
    if (error && error.code !== 404) {
      return response.status(error.code).send(error.msg);
    }

    if (error) {
      email = null;
    }

    return response.status(200).send({ email });
  });
};

OAE.globalAdminRouter.on('get', '/api/user/:userId/email/token', _getEmailToken);
OAE.tenantRouter.on('get', '/api/user/:userId/email/token', _getEmailToken);

/**
 * @REST deleteUserIdEmailToken
 *
 * Delete a pending email token for a user
 *
 * @Server      admin,tenant
 * @Method      DELETE
 * @Path        /user/{userId}/email/token
 * @PathParam   {string}        userId                      The id of the user to for which to delete the pending email token
 * @HttpResponse                200                         The token has been deleted
 * @HttpResponse                400                         Invalid user id
 * @HttpResponse                401                         You do not have sufficient rights to delete this user's email token
 * @HttpResponse                404                         There is no pending token for the user
 */
const _deleteEmailToken = function (request, response) {
  PrincipalsAPI.deleteEmailToken(request.ctx, request.params.userId, (error) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    response.status(200).end();
  });
};

OAE.globalAdminRouter.on('delete', '/api/user/:userId/email/token', _deleteEmailToken);
OAE.tenantRouter.on('delete', '/api/user/:userId/email/token', _deleteEmailToken);

/**
 * Extract the optional user profile parameters from the given set of request parameters
 *
 * @param  {Object}     parameters  The parameters from which to extract the profile information
 * @return {Object}                 The relevant parameters for user profiles
 * @api private
 */
const _getOptionalProfileParameters = function (parameters) {
  return {
    visibility: parameters.visibility,
    email: parameters.email,
    emailPreference: parameters.emailPreference,
    locale: parameters.locale,
    publicAlias: parameters.publicAlias,
    acceptedTC: parameters.acceptedTC === 'true'
  };
};

/**
 * @REST getRecentGroupsForUserId
 *
 * Get the recently visited groups of a principal
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /user/{userId}/recent
 * @PathParam   {string}        userId                      The id of the principal for which to get the recent groups
 * @Return      {RecentGroupsResponse}                      The principal's recently visited groups
 * @HttpResponse                200                         Recent groups available
 * @HttpResponse                400                         Must specify a valid principalId
 * @HttpResponse                401                         You do not have access to this principal's groups
 * @HttpResponse                404                         The specified user could not be found
 */
OAE.tenantRouter.on('get', '/api/user/:userId/groups/recent', (request, response) => {
  const limit = 5;
  PrincipalsAPI.getRecentGroupsForUserId(request.ctx, request.params.userId, limit, (error, results) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send({ results });
  });
});

/**
 * @REST getUsersForTenant
 *
 * Get all users for a given tenant
 *
 * @Server      admin,tenant
 * @Method      GET
 * @Path        /tenants/{tenantAlias}/users
 * @PathParam   {string}        tenantAlias                 The id of the user to get
 * @Return      {User[]}                                    A list of users for the tenant
 * @HttpResponse                200                         Users available
 * @HttpResponse                400                         Must provide a tenantAlias
 * @HttpResponse                404                         No users were found for the given tenantAlias
 */
const _getUsersForTenant = function (request, response) {
  PrincipalsAPI.getAllUsersForTenant(request.ctx, request.params.tenantAlias, (error, users) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    return response.status(200).send(users);
  });
};

OAE.globalAdminRouter.on('get', '/api/tenants/:tenantAlias/users', _getUsersForTenant);
OAE.tenantRouter.on('get', '/api/tenants/:tenantAlias/users', _getUsersForTenant);

/**
 * @REST exportData
 *
 * Download personnal data of a principal
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /user/{userId}/export/{exportType}
 * @PathParam   {string}        userId                      The id of the principal for which to get his datas
 * @PathParam   {string}        exportType                  Export type can be 'personal-data', 'content' or 'shared'
 * @Return      {File}                                      Zip file with all personal data related to a user
 */
OAE.tenantRouter.on('get', '/api/user/:userId/export/:exportType', (request, response) => {
  PrincipalsAPI.exportData(request.ctx, request.params.userId, request.params.exportType, async (error, zipFile) => {
    if (error) {
      return response.status(error.code).send(error.msg);
    }

    const filename = 'myData.zip';
    response.setHeader('Content-Disposition', 'attachment; filename="' + filename + '"');
    response.setHeader('Content-Type', 'application/zip');
    response.writeHead(200);

    const nodebuffer = await zipFile.generateAsync({
      type: 'nodebuffer',
      platform: process.platform,
      streamFiles: true
    });
    response.end(nodebuffer);
  });
});