This is an old revision of the document!


User type

User type (projection) was added in CzechIdM version 10.2.0. Projection defines frontend form to read, create and edit user. We can create and edit user by different form. For example externe and internal employee can be created and edited differently (different attributes has to be filled). Used projection for user creation is set as user type.

Provided projection can be configured by administrator. New projection type and localization can be provided by developer.

Configurable form (projection) with configurable features is provided in product:

  • Show user personal data - show selected or all user personal data (e.g. login, first name, surname).
  • Show contract - prime contract is shown by default. If currenly logged user has permission to read prime contract. First other contract is shown otherwise. Contract are sorted by priority the same way, as prime contract is evaluated.
  • Show other contract position - first contract other position is shown by default.
  • Show extended identity attributes - show selected extended attributes from selected form definitions.
  • Show extended contract attributes - show selected extended attributes from selected form definitions. Contract are sorted by priority. Prime contract extended attributes can be edited, if logged user can read and edit prime contract. If logged user cannot read prime contract, next contract which identity can read is shown.
  • Set or change user password - user can be created with or without password. Link to password change is shown for edited user.
  • Request to change roles - roles can be requested for newly added user. Assigned roles are shown with button to change assigned roles by role request for edited user.
Authorization policies has to be configured to see all projection features. For example, if currently logged user cannot read contracts, then contract will be hidden.

New projection can be configured from agenda SettingForm definitionsForm projectionsAdd button.

We can configure new projection to introduce all features:

  • Code - Externe user. Projection simple name.
  • Module - we can leave it empty, core module is used by default.
  • Frontend target - we can leave it by default /form/identity-projection. This target leads to prepared product projection page on frontend. Only developer can register new target page.
  • Basic attributes - select User name, First name, Surname.
  • Form definitions - we need to prepare extended attributes form definition before to use then here, we will use this definitions and attributes as example:
    • default - IdmIdentity - default form definition for users, select attributes:
      • Mobile phone - text attribute
      • Reqistration - boolean attribute
    • default - default - IdmIdentityContract - default form definition for cntracts, select attributes:
      • Environment - codelist ith available environments
      • Manager - user select box
  • Description - Create and edit externe user. Description is shown in projection list (into about projection and for filter).
  • Inactive - inactive projection will not be available for creating new user.

We filled simple projection name as projection code. Projection name will be shown without localization. Read how to add localization
Authorization policies have to be configured to read and edit extended attributes by admin and user. Read how to configure authorization policies.
Attributes generated by system can be hidden. For example we not provide to set or change user password - we expect password will be generated after user will be provisioned on target system (one password will be set to all user account and for IdM too). Read how to set or change user password by product projection.

When new user is created (menu UsersCreate user button.), then newly created projection can be chosen:

Default form can be used too as default. Read how to hide this option by configuration, if needed.

User detail for create user by configuration above

We can create new user with all attributes are filled. We can choose roles, which will have to be assigned (requested) for created user. Used form (projection) is set as user type. After form is saved, the same form is shown and user can be edited if needed. The same form is used and shown as user detail.

User detail for edit user by configuration above

Default full detail

Default full detail can be shown for each user with projection usage. New button was added into user (and projection) detail header:

Button is available for all logged user without any additional permission is needed. Quick link to default full detail is available from top profile menu. Buttons on dashboard ane any other link to user detail will lead to projection form.

The same way is posible to go back to form by projection from full default detail. Button is in the same place with different direction.

User type (projection) can be changed from default full identity detail:

After user type is changed and user is saved, then button in detail header (see above) can be used for show user in form projection.

Authorization policies have to be configured to enable change user type.

If password attribute is added in projection personal data configuration, then:

  • New password can be filled fo newly created user,
  • link to change password is available for edited user.
Password change has to be enabled by configuration.
Authorization policies have to be configured to enable password change.

New roles can be requested for newly added user. If user is edited, then assigned roles are shown with button to change assigned roles by role request.

Identity roles are assigned asynchronously, so pending role requests are shown together with assigned roles. Assigned roles are refreshed automatically, when asynchronous role request is completed.

Authorization policies have to be configured to enable this feature.

In the application profile (application.properties) and overloadable via ConfigurationService.

# show default form for newly created user 
# default form can be disabled => at least one configured form projection is needed
idm.pub.app.show.identity.formProjection.default=true
#
# default password change type for custom users, one of values: 
# DISABLED - password change is disable
# ALL_ONLY - users can change passwords only for all accounts
# CUSTOM - users can choose for which accounts change password
# Needed on FE (=> public) 
idm.pub.core.identity.passwordChange=CUSTOM

Administrator

Aministrator for create, edit and delete configured form projection.

  • Permission to autocomplete and read form definitions: Forms - definitions (IdmFormDefiniton) | View in select box (autocomplete), Read | BasePermissionEvaluator
  • Permission to autocomplete and read form attributes: Forms - attributes (IdmFormAttribute) | - | FormAttributteByDefinitionEvaluator
  • Permission to admin form projections: Forms - projections (IdmFormProjection) | Administration (all) | BasePermissionEvaluator

Manager

Manager for create and edit user with form projection usage with all features enabled.

  • Permission to work with identities: Users (IdmIdentity) | View in select box (autocomplete), Create, Read, Update, Change roles, Change user type (projection), Password change | BasePermissionEvaluator
  • Permission to read contracts according to identity: Industrial relations (IdmIdentityContract) | - | IdentityContractByIdentityEvaluator
  • Permission to read other contract positions according to contract: Other contract positions (IdmContractPosition) | - | ContractPositionByIdentityContractEvaluator
  • Permission to read the assigned identity roles: Roles assigned to users (IdmIdentityRole)| - | IdentityRoleByIdentityEvaluator
  • Permission to request roles (which can be requested): Role (IdmRole) | Can be requested | RoleCanBeRequestedEvaluator
  • Permission to read role requests according to identity: Requests for assigned roles (IdmRoleRequest) | - | RoleRequestByIdentityEvaluator
  • Permission to autocomplete form projections: Forms - projections(IdmFormDefiniton) | View in select box (autocomplete) | BasePermissionEvaluator
  • Permission to autocomplete form definitions: Forms - projections(IdmFormDefiniton) | View in select box (autocomplete) | BasePermissionEvaluator
  • Permission to read and update all identity attributes in main definition: Forms - values (IdmIdentityFormValue) | View in select box (autocomplete) | IdentityFormValueEvaluator
  • Permission to read and update all contract attributes in main definition: Forms - values (IdmIdentityContractFormValue) | View in select box (autocomplete) | IdentityContractFormValueEvaluator
  • Enabling the autocomplete for entities:
    • Role (IdmRole) | Displaying in autocomplete, selections | BasePermissionEvaluator
    • Structure types (IdmTreeType) | View in select box (autocomplete) | BasePermissionEvaluator
    • Tree nodes (IdmTreeNode) | View in select box (autocomplete) | BasePermissionEvaluator
    • Accounts (AccAccount) | - | ReadAccountByIdentityEvaluator (← use this only when using acc module)
    • Identity accounts (AccIdentityAccount) | - | IdentityAccountByAccountEvaluator (← use this only when using acc module)

Manager - create and edit identity only

Manager for create and edit user with form projection usage only (simplest setting).

  • Permission to work with identities: Users (IdmIdentity) | Create, Read, Update | BasePermissionEvaluator
  • Permission to autocomplete form projections: Forms - projections(IdmFormDefiniton) | View in select box (autocomplete) | BasePermissionEvaluator

Normal user

Other user doesn't need any additional autorization policy configuration. Form projection is saved together with user and when user detail is shown, then this projection is used.

TODO: move to developer guide

Two projections are localized by default in product. If projection with code identity-externe or identity-internal will be configured, then localization will be used.

...
  "eav": { 
    "form-projection": {
      "identity-externe": {
        "label": "Externe user",
        "help": "Create externe user",
        "icon": "fa:walking",
        "level": "primary"
      },
      "identity-internal": {
        "label": "Internal employee",
        "help": "Create internal user",
        "icon": "fa:user",
        "level": "success"
      }
    }
  },
...

Localization can be added to newly configured projection in custom module. Projection localization is based on the same convetions as localization for [form definition].

Supported properties:

  • label - projection name. Used in select boxes and as label for button to create user with projection usage.
  • help - Used as title (tooltip) for button to create user with projection usage.
  • icon - Used in select boxes and as icon for button to create user with projection usage (optional, fa:user-plus will be used as default).
  • level - Used as level for button to create user with projection usage (optional, default will be used as default).

Form projection with configurable features was added into product core module:

  • Frontend:
    • Form projection content is placed on path src/content/identity/projection/IdentityProjection.js.
    • Route (= frontent target) form/identity-projection/:entityId' is registed in routes.js.
    • Component identity-projection with route usage is registered in component-descriptor.js
  • Backend:
    • IdmIdentityProjectionDto was added - contains all projection data sent between backend and frontend, contains:
      • identity - identity.
      • contract - first (~ prime) contract. If currenly logged user has permission to read prime contract. First other contract is shown otherwise. Contract are sorted by priority the same way, as prime contract is evaluated.
      • otherContracts - all other contracts.
      • otherPositions - all other positions.
      • identityRoles - all assigned identity roles.
    • IdentityProjectionManager was added and support get and save identity projection. Projection is processed by event and processors (see IdentityProjectionSaveProcessor). Processors can be reqisted to process IdmIdentityProjectionDto content (custom validations, generators etc. can be added non invasively). Manager contains overridable protected method to get and save all parts of projection (methods can be overidden invasively to add or change product behavior).
    • IdmIdentityProjectionController is exposed on url '<server api>/identity-projection' and support get and post IdmIdentityProjectionDto - calls IdentityProjectionManagermethods.

New projection can be added with the same structure in custom module. Is needed to add new content representing form projection (e.g. copy or reuse form projection from product), register new route in routes.js, register new component in component-descriptor.js and expose new edpoint on backend if needed (if provided IdmIdentityProjectionDto is insuffiscient).

Projection will obtain entityId route parameter with identity codeable identifier (username or uuid).

Example

Source codes are placed in example module. Product backend is reused and example provides new frontend form for new projection. Form saves identity username only.

Create new form src/content/identity/projection/ExampleIdentityProjection.js with content:

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
//
import { Basic, Advanced, Utils, Managers, Content } from 'czechidm-core';
 
const identityManager = new Managers.IdentityManager();
const identityProjectionManager = new Managers.IdentityProjectionManager();
const formProjectionManager = new Managers.FormProjectionManager();
 
 
/**
 * Example  form for identity projection..
 *
 * @author Radek Tomiška
 * @since 10.2.0
 */
class ExampleIdentityProjection extends Basic.AbstractContent {
 
 
  constructor(props, context) {
    super(props, context);
    this.state = {
      generatePassword: false,
      generatePasswordShowLoading: false
    };
  }
 
  getContentKey() {
    return 'content.identity.projection';
  }
 
  getNavigationKey() {
    return 'identities';
  }
 
  componentDidMount() {
    super.componentDidMount();
    //
    const { entityId } = this.props.match.params;
    const { identityProjection, location } = this.props;
    const isNew = !!Utils.Ui.getUrlParameter(location, 'new');
    //
    if (isNew) {
      let formProjectionId;
      if (identityProjection && identityProjection.identity && identityProjection.identity.formProjection) {
        formProjectionId = identityProjection.identity.formProjection;
      } else {
        formProjectionId = Utils.Ui.getUrlParameter(this.props.location, 'projection');
      }
      if (!formProjectionId) {
        // form projection not found - default will be shown
        this._initProjection(entityId, identityProjection, {});
      } else {
        // fetch projection definition
        this.context.store.dispatch(formProjectionManager.autocompleteEntityIfNeeded(formProjectionId, null, (entity, error) => {
          if (error) {
            this.addError(error);
          } else {
            this._initProjection(entityId, identityProjection, entity);
          }
        }));
      }
    } else {
      this.getLogger().debug(`[FormDetail] loading entity detail [id:${ entityId }]`);
      this.context.store.dispatch(identityProjectionManager.fetchProjection(entityId, null, (entity, error) => {
        if (error) {
          this.addError(error);
        } else {
          this._initProjection(entityId, entity);
        }
      }));
    }
  }
 
  _initProjection(entityId, identityProjection = null, formProjection = null) {
    //
    // prepare form friendly projection
    let _identityProjection = null;
    if (identityProjection) {
      if (!formProjection && identityProjection.identity && identityProjection.identity._embedded) {
        formProjection = identityProjection.identity._embedded.formProjection;
      }
      //
      _identityProjection = {
        ...identityProjection.identity
      };
    } else {
      // new projection
      _identityProjection = {
        id: entityId,
        identity: {
          id: entityId,
          formProjection: formProjection ? formProjection.id : Utils.Ui.getUrlParameter(this.props.location, 'projection')
        }
      };
    }
    //
    this.setState({
      identityProjection: _identityProjection,
      formProjection
    }, () => {
      if (this.refs.username) {
        this.refs.username.focus();
      }
    });
  }
 
  save(event) {
    if (event) {
      event.preventDefault();
    }
    if (!this.refs.form.isFormValid()) {
      return;
    }
    const { identityProjection } = this.state;
    //
    this.refs.form.processStarted();
    const data = this.refs.form.getData();
    // construct projection
    const _identityProjection = {
      ...identityProjection,
      id: data.id,
      identity: data
    };
    //
    // post => save
    this.context.store.dispatch(identityProjectionManager.saveProjection(_identityProjection, null, this._afterSave.bind(this)));
  }
 
  /**
  * Just set showloading to false and set processEnded to form.
  * Call after save/create
  */
  _afterSave(identityProjection, error) {
    if (error) {
      this.setState({
        validationError: error,
        validationDefinition: false
      }, () => {
        this.addError(error);
        if (this.refs.form) {
          this.refs.form.processEnded();
        }
      });
    } else {
      this.addMessage({
        message: this.i18n('action.save.success', { record: identityProjection.identity.username, count: 1 })
      });
      this.context.history.replace(identityManager.getDetailLink(identityProjection.identity));
      if (this.refs.form) {
        this.refs.form.processEnded();
      }
    }
  }
 
  render() {
    const {
      match,
      location,
      showLoading,
      _imageUrl
    } = this.props;
    const { entityId } = match.params;
    const {
      identityProjection,
      formProjection
    } = this.state;
    const isNew = !!Utils.Ui.getUrlParameter(location, 'new');
    //
    return (
      <Basic.Div>
        <Helmet title={ isNew ? this.i18n('create.title') : this.i18n('edit.title') } />
        <Basic.Row>
          <Basic.Div className="col-lg-offset-2 col-lg-8">
            <Advanced.DetailHeader
              entity={ isNew ? null : identityProjection }
              back="/identities"
              buttons={[
                <Basic.Icon
                  value="fa:angle-double-right"
                  style={{ marginRight: 5, cursor: 'pointer' }}
                  title={ this.i18n('component.advanced.IdentityInfo.link.detail.default.label') }
                  onClick={ () => this.context.history.push(`/identity/${ encodeURIComponent(identityProjection.username) }/profile`) }
                  rendered={ !isNew }/>
              ]}>
              {
                _imageUrl
                ?
                <img src={ _imageUrl } alt="profile" className="img-circle img-thumbnail" style={{ height: 40, padding: 0 }} />
                :
                <Basic.Icon
                  icon={ formProjection ? formProjectionManager.getLocalization(formProjection, 'icon', 'component:identity') : 'component:identity' }
                  identity={ identityProjection }/>
              }
              { ' ' }
              { identityProjectionManager.getNiceLabel(identityProjection) }
              <small>
                { ' ' }
                { isNew ? this.i18n('create.title') : this.i18n('edit.title') }
              </small>
            </Advanced.DetailHeader>
 
            <Content.OrganizationPosition identity={ entityId } rendered={ !isNew }/>
 
            <form onSubmit={ this.save.bind(this) }>
 
              <Basic.Panel rendered={ identityProjection === null || identityProjection === undefined }>
                <Basic.Loading isStatic show/>
              </Basic.Panel>
 
              <Basic.Panel className={ identityProjection === null || identityProjection === undefined ? 'hidden' : 'last' }>
 
                <Basic.PanelBody>
                  <Basic.AbstractForm
                    ref="form"
                    data={ identityProjection }
                    readOnly={ !identityProjectionManager.canSave(isNew ? null : identityProjection) }
                    style={{ padding: 0 }}>
 
                    <Basic.TextField
                      ref="username"
                      label={ this.i18n('identity.username.label') }
                      max={ 255 }/>
 
                  </Basic.AbstractForm>
                </Basic.PanelBody>
                <Basic.PanelFooter>
                  <Basic.Button type="button" level="link" onClick={ this.context.history.goBack }>
                    { this.i18n('button.back') }
                  </Basic.Button>
                  <Basic.Button
                    type="submit"
                    level="success"
                    showLoading={ showLoading }
                    showLoadingIcon
                    showLoadingText={ this.i18n('button.saving') }
                    rendered={ identityProjectionManager.canSave(isNew ? null : identityProjection) }>
                    { this.i18n('button.save') }
                  </Basic.Button>
                </Basic.PanelFooter>
              </Basic.Panel>
            </form>
          </Basic.Div>
        </Basic.Row>
      </Basic.Div>
    );
  }
}
 
ExampleIdentityProjection.propTypes = {
  identityProjection: PropTypes.object
};
ExampleIdentityProjection.defaultProps = {
  identityProjection: null,
  _imageUrl: null
};
 
function select(state, component) {
  const { entityId } = component.match.params;
  const profileUiKey = identityManager.resolveProfileUiKey(entityId);
  const profile = Managers.DataManager.getData(state, profileUiKey);
  const identityProjection = identityProjectionManager.getEntity(state, entityId);
  //
  return {
    identityProjection,
    showLoading: identityProjectionManager.isShowLoading(state, null, !identityProjection ? entityId : identityProjection.id),
    _imageUrl: profile ? profile.imageUrl : null,
    passwordChangeType: Managers.ConfigurationManager.getPublicValue(state, 'idm.pub.core.identity.passwordChange')
  };
}
 
export default connect(select)(ExampleIdentityProjection);

Register new route in 'routes.js':

{
  path: 'example/form/identity-projection/:entityId',
  component: require('./src/content/identity/projection/ExampleIdentityProjection'),
  access: [ { type: 'HAS_ANY_AUTHORITY', authorities: ['IDENTITY_READ' ] } ]
}

Register new component in component-descriptor.js

{
  id: 'example-identity-projection',
  type: 'form-projection',
  ownerType: 'IdmIdentity',
  route: '/example/form/identity-projection'
}

Skip user dashboard

If we want to show user detail immediatelly (skip user dasboard or skip info card), we can hold ctrl key when clicking on user link (or info card).

TODO:

  • split to developer guide + admin tutorial (how to create )
  • by tomiskar