6. User Accounts and Security

Previous chapter

Next chapter

This chapter also shows you how to perform some common GemStone user administration tasks:

To perform most of these tasks you must have explicit privilege to execute a restricted Smalltalk method, and you may also need to be explicitly authorized to modify an affected GsObjectSecurityPolicy. This chapter introduces these concepts. For a full description of privileges and GsObjectSecurityPolicies, see the chapter of the Programming Guide that discusses security.

6.1  GemStone Users

This section provides background information about how GemStone stores user accounts, what accounts are predefined, and what determines an account’s name space.

UserProfiles

Each GemStone user is associated with an instance of class UserProfile. This UserProfile object contains information describing objects that the user is allowed to examine or modify, privileges that the user has to perform certain operations, and security information.

Each UserProfile has the following information:

User ID

A unique String that identifies the user to the GemStone system.

Password

The GemStone-specific password (an InvariantString) to use to validate logins. GemStone stores the password in encrypted form in a secure manner.

Default Object Security Policy

Either nil or an instance of GsObjectSecurityPolicy. This determines the default read and write authorizations for objects created by the user.

Privileges

A collection of symbols that allow the user to perform certain “privileged” system functions.

Groups

In conjunction with object security policies, group membership is used to allow access to restricted objects for specific categories of users.

SymbolList

The list of SymbolDictionaries that this user can see.

Login Hook

Method selector or block of code to execute on login.

These are discussed in more detail under UserProfile Data.

Other information related to the user account is stored in an instance of UserSecurityData; this includes data related to security features. Instances of UserSecurityData are private and protected, but some information, such as lastLoginTime, can be accessed via methods in UserProfile.

AllUsers

Each instance of UserProfile must be in the global collection, AllUsers. AllUsers is the single instance of UserProfileSet. AllUsers acts as the “root” for all objects in the repository; any object in the repository must be reachable from AllUsers, usually via the SymbolLists of the UserProfiles, otherwise it is subject to garbage collection.

Special System Users

When GemStone is first installed, AllUsers has UserProfiles already defined for the following users. These are the special system users. You must never delete these users. These users may not have privileges removed, cannot be disabled, must use GemStone authentication, and their accounts are not subject to password or account age limits.

You can determine if an account is a special system user by executing:

UserProfile isSpecialUserId: 'theUserId'
SystemUser

SystemUser is analogous to root in UNIX. SystemUser has all privileges, belongs to all predefined groups, and is authorized to read and write all objects regardless of GsObjectSecurityPolicy protection. These privileges cannot be taken away, so SystemUser can always write to all objects. This account is used only to perform GemStone system upgrades, modify some system configuration settings, and other special-purpose operations that must be highly restricted.

The SystemUser account is the owner of the SystemObjectSecurityPolicy, which contains the kernel classes.

WARNING
Logging in to GemStone as SystemUser is like logging in to your workstation as root: an accidental modification to a kernel object can cause a great deal of harm. Use the DataCurator account for system administration functions except those that require SystemUser privileges, such as a repository upgrade.

DataCurator

The DataCurator account is the account that is normally used for day-to-day administration tasks. Initially, DataCurator is granted all privileges and belongs to all predefined groups. All GemStone UserProfiles are protected by the DataCuratorObjectSecurityPolicy.

GcUser

The GcUser account is a special account that logs in to the repository automatically to perform garbage collection tasks. You normally would only login as GcUser in order to update configuration parameters stored in GcUser’s UserGlobals.

SymbolUser

The SymbolUser account is a special account that is used to perform symbol creation tasks. Login as SymbolUser is disallowed. Directly accessing the AllSymbols collection, which is in the UserGlobals of the SymbolUser, is possible from another administrative session.

Nameless

The Nameless account is a special account for use only by other GemStone products. Do not use this account or change it unless instructed to do so by GemStone Technical Support.

6.2  Creating and Removing Users

Methods that create UserProfiles add the new UserProfile to AllUsers, the global collection (UserProfileSet) of users. UserProfiles that are not in AllUsers cannot log in.

In addition to creating the new UserProfile, you should also see that each user’s UNIX environment is set up to provide access to GemStone. This is described in the GemStone/S 64 Bit Installation Guide.

Removing a user, in addition to removing the UserProfile from AllUsers, requires cleanup to ensure that objects that refer to the deleted user do not inadvertently prevent the deleted user from being garbage collected, and that objects that are referred to only by the deleted users are not inadvertently garbage collected. The methods to remove users perform this cleanup.

UserProfile Data

Each user profile has the following instance variable data, either explicitly specified during instance creation, or provided with default values. This information can be updated.

The requirements for updating this information vary. In many cases, the requirements are different between updating information for another user and for updating your own information. See the update methods for details.

User ID

Each UserProfile is created with a unique user Id String. Embedded spaces are permitted.

UserId may only be changed by SystemUser or by a user with the privilege ChangeUserId. The userIds of special system users cannot be changed.

Password

Each UserProfile is created with an initial password, an Invariant String, which must not be the same as the User Id and may not be longer than 1024 characters. The user supplies this password for identification purposes at login, unless another form of login authentication is used; see Password Authentication.

Users must have the explicit privilege #UserPassword to change their own passwords, and there are a number of ways to constrain the choice of passwords. See the section Limiting Choice of Passwords for details.

To change the password of a user other than yourself, the #OtherPassword privilege is required, a different method is used, and password constraints do not apply.

Default Object Security Policy

A users’ defaultObjectSecurityPolicy determines the default read and write authorizations for objects created by the user. When you add a new user to the GemStone system, you can either allow the default security policy to be nil, use protocol that creates a new GsObjectSecurityPolicy, or specify an existing GsObjectSecurityPolicy for the user.

For more information on how security policies are used to control read and write authorization for objects in the repository, see the chapter in the Programming Guide that discusses security.

A defaultObjectSecurityPolicy of nil means that objects created by that user, by default, have world read and write access; that is, are not restricted from being read or written by all other users. Not requiring authorization checks has the benefit of improved performance, if your application does not require object level security.

To specify a defaultObjectSecurityPolicy for a user, you may use an existing instance of GsObjectSecurityPolicy, or you must create and commit the GsObjectSecurityPolicy before it can be used.

To modify the defaultObjectSecurityPolicy of any user, you must have write access to the security policy of that UserProfile. In addition, to modify your own defaultObjectSecurityPolicy, you must have the DefaultObjectSecurityPolicy privilege.

Privileges

When you create a new UserProfile, you determine whether the new user may perform certain “privileged” system functions. For example, stopping another session or the repository itself requires a particular privilege to do so. Table 6.1 describes the types of functions that each privilege controls.

For developers, you must also specifically grant the privilege that allows the user to modify code.

Note that privileges are more powerful than security policy authorization. Although the owner of a security policy can always use authorization protocol to restrict read or write access to objects in a policy, an administrator with appropriate privileges, such as DataCurator, can override that protection by sending privileged messages that let you change the authorization scheme.

Table 6.1 GemStone Privileges

Type of Privilege

Privileged Operations

SystemControl

SystemControl is required by methods that start or stop sessions, including operations that invoke the Multi-Threaded Scan; for methods that suspend or resume logins, send signals to other sessions, and manage checkpoints.

SessionAccess

SessionAccess privilege is required to find out information about sessions other than the current session, or to perform operations on other sessions.

UserPassword

Required to change your own password using UserProfile>>oldPassword:newPassword:

DefaultObjectSecurityPolicy

This privilege is required to set a UserProfile’s default ObjectSecurityPolicy using UserProfile>>defaultObjectSecurityPolicy: or a method that invokes that.

For compatibility with previous versions, the DefaultSegment privilege also resolves to this privilege

CodeModification

You must have CodeModification privilege to create or modify instances of GsNMethod, GsMethodDictionary, or Class. See the discussion following this table.

OtherPassword

You must have OtherPassword privilege to make any changes to a UserProfile. This includes adding or removing a SymbolDictionary to/from a SymbolList that is not your own. OtherPassword is also required to find out information about UserProfiles other than the currently logged in session.

OtherPassword is required to make any changes to AllUsers, including creating a new user and configuring security requirements.

ObjectSecurityPolicyCreation

Required in order to creating a new GsObjectSecurityPolicy, using GsObjectSecurityPolicy class>>new, newInRepository: or any methods that invoke these.

For compatibility with previous versions, the SegmentCreation privilege also resolves to this privilege.

ObjectSecurityPolicyProtection

You must have ObjectSecurityPolicyProtection to update the authorizations of a GsObjectSecurityPolicy, other than one that is owned by the current session’s user. This includes GsObjectSecurityPolicy>>group:authorization:, ownerAuthorization:, and worldAuthorization:.

For compatibility with previous versions, the SegmentProtection privilege also resolves to this privilege.

FileControl

FileControl is required for operations that access external files, including operations related to backup, restore, transaction logs, and extents.

GarbageCollection

Required to perform any garbage collection operation, to start and stop Admin and Reclaim Gems, and force epoch or reclaim to run. Also required to audit and profile the repository.

NoPerformOnServer

If you have this privilege, you cannot execute System class>>performOnServer:

NoUserAction

If you have this privilege, you cannot execute System class>> loadUserActionLibrary:

NoGsFileOnServer

If you have this privilege, you cannot execute ny GsFile operation which accesses a file on the server.

NoGsFileOnClient

If you have this privilege, you cannot execute any GsFile operation which accesses a file on the client.

SessionPriority

Required to modify the priority of any session, or to check the priority of a session other than the current session.

CompilePrimitives

Allow user to compile primitive methods, which is otherwise restricted to SystemUser.

ChangeUserId

Allow user to execute userId:password: to rename a user, which is otherwise restricted to SystemUser.

Code Modification Privilege

CodeModification privilege is required to execute any method that modifies Smalltalk code:

  • You must have CodeModification privilege to create instances of GsNMethod, or to create or modify instances of GsMethodDictionary or Class. (You cannot modify a GsNMethod once it has been created.)
  • You must have CodeModification privilege to add a Class to, or remove a Class from, a SymbolDictionary and its subclasses.
  • You must have CodeModification privilege to add or remove a SymbolDictionary from your own SymbolList.

You cannot use GemBuilder for C to modify instances of the following classes (or their subclasses): GsNMethod, GsMethodDictionary, Class, SymbolDictionary, SymbolList, UserProfile.

Groups

GemStone uses group membership to facilitate access to objects that are protected by GsObjectSecurityPolicies. While certain objects must be protected from read or write access by other users in the system (the “world”), you may still need to grant access to specific individual users. By adding group authorization to the GsObjectSecurityPolicy that protects these objects, and adding the corresponding group membership to that user, you can provide a user with the appropriate access to these objects.

A GsObjectSecurityPolicy can authorize multiple groups and a user can be a member of multiple groups. There are several predefined groups, as shown in Table 6.2. By default, all new users become members of group Subscribers.

AllGroups

AllGroups is a global collection of Strings, that includes all groups defined for all users and all security policies.

Initially, AllGroups contains the following:

Table 6.2 GemStone Groups

Group name

Access

System

Members of this group have write access to objects protected by the GcUser’s object security policy.

DataCuratorGroup

Members of this groups have write access to objects protected by the DataCuratorObjectSecurityPolicy. This is useful if you wish to make a user other than DataCurator to be a system administrator, since many operations that update users require write access to DataCuratorObjectSecurityPolicy.

Publishers

Members of this group have write access to objects protected by PublishedObjectSecurityPolicy.

Subscribers

Members of this group have read access to objects protected by PublishedObjectSecurityPolicy.

SymbolUser

(Reserved).

Symbol Lists

As explained in the Programming Guide, the GemStone Smalltalk compiler follows a well-defined path in resolving objects named by source code symbols. First, the compiler considers the possibility that a variable name might be either local (a temporary variable or an argument) or defined by the class of the current method definition (an instance variable, a class variable, or a pool variable). If a variable is none of these, the compiler refers to an Array of SymbolDictionaries in the user’s UserProfile and current session state. That Array is called the user’s symbol list. The symbol list tells Smalltalk which of many possible GemStone SymbolDictionaries to search for an object named in a Smalltalk program.

For each user, a persistent instance of class SymbolList is stored in the repository and is referenced from the UserProfile associated with this user as the symbolList instance variable. In addition, a transient copy of that SymbolList is stored in the GsCurrentSession object for the logged-in session.

A session’s transient copy can be modified without affecting (or causing concurrency conflicts with) either the persistent symbol list or the transient copies controlling other sessions. Changes to your own UserProfile’s persistent symbol list also change the symbol resolution of your current session. However, changes to the persistent symbol list are likely to cause concurrency conflicts with other sessions logged in under the same userId.

For further information about symbol lists, refer to the Programming Guide.

New UserProfiles are created with the following SymbolDictionaries, in this order:

UserGlobals

Each UserProfile has its own SymbolDictionary for the user’s private symbols.

Globals

The second element in each user’s initial symbol list is a “system globals” SymbolDictionary, Globals. This dictionary contains all of the GemStone Smalltalk kernel classes (Object, Class, Collection, Integer, and so forth). Although users can read the objects in Globals, ordinarily they cannot modify objects in that Dictionary.

Published

The third and final element in each user’s initial symbol list is a SymbolDictionary for application objects that are “published” to all users. Users who are members of the group Publishers can place objects in this dictionary to make them visible to other users. Using the Published dictionary lets you share these objects without having to put them in Globals, which contains the GemStone kernel classes, and without the necessity of adding a special dictionary to each user’s symbolList instance variable.

Although all users automatically share access to objects in Globals, sharing application objects between users requires that the objects be in a SymbolList that is visible to both users. There are three primary ways to do this:

  • As a member of group Publishers, you can add the objects to the Published dictionary. This dictionary is already in each user’s symbol list, so whatever you add becomes visible to users the next time they obtain a fresh transaction view of the repository. You may do this by sending the message Published at: aKey put: aValue.
  • You can define a special SymbolDictionary, and add that to the user’s SymbolList. The procedure is described under Adding a SymbolDictionary to Someone Else’s Symbol List.
  • The application itself can add the objects to the individual user’s symbol list, either to the permanent symbol list in the UserProfile or to a transient symbol list for that session. For information about this approach, refer to the Programming Guide.

For more information, refer to the chapter on symbol resolution and object sharing in the Programming Guide.

Creating Users

Privileges required: OtherPassword, and write access to the DataCuratorObjectSecurityPolicy. You may also need ObjectSecurityPolicyCreation.

Simple User Creation

At minimum to create a new UserProfile, you must supply the new user’s userId and password, each as a String. This creates the new user with no privileges, only the Subscribers group, and with a nil defaultObjectSecurityPolicy.

AllUsers addNewUserWithId: 'theUserId' 
	password: 'thePassword'.
Simple User Creation with GsObjectSecurityPolicy Creation

To create a new user and specify that a new instance of GsObjectSecurityPolicy should be created for the new user, use the following expression:

AllUsers addNewUserWithId: 'theUserId' 
	password: 'thePassword'
	createNewObjectSecurityPolicy: true
User Creation With Privileges, Groups, and ObjectSecurityPolicy

Using the complete form allows you to assign privileges to the new user, add the user to groups, and specify an ObjectSecurityPolicy. The ObjectSecurityPolicy may be nil.

AllUsers addNewUserWithId: 'theUserId'
    password: 'thePassword'
    defaultObjectSecurityPolicy: anObjectSecurityPolicyOrNil
    privileges: anArrayOfPrivStrings
    inGroups: aCollectionOfGroupStrings

For example:

topaz 1> printit
AllUsers addNewUserWithId: 'Mary'
	password: 'herPasswd'
	defaultObjectSecurityPolicy: nil
	privileges: #( 'UserPassword' )
	inGroups: #( 'MarathonRunners' ).
System commitTransaction.
%

For additional user creation protocol, see the image. UserProfileSet instance methods and UserProfile class methods both create new UserProfiles and add the new user to AllUsers.

Removing Users

Privileges required: OtherPassword.

In addition to removing the UserProfile from AllUsers, removing a user requires that any GsObjectSecurityPolicies owned by the deleted user are moved to a new user.

In addition, to ensure that work being done by the user being deleted is not lost, the SymbolLists of the deleted user are moved to a separate location where they can be periodically reviewed and manually dereferenced.

DeletedUserProfile and AllDeletedUsers

When a user is removed, a new instance of DeletedUserProfile is created, with the userId and the SymbolLists of the user being removed, and the current DateTime.

This instance of DeletedUserProfile is moved to a collection #AllDeletedUsers, in Globals SymbolDictionary.

An administrator should review the classes and methods in the SymbolLists of the DeletedUserProfile, copy anything valuable elsewhere, and manually remove the DeletedUserProfile from AllDeletedUsers.

Remove User, with ObjectSecurityPolicies Going to the Current User

The following methods remove the user and reassign any GsObjectSecurityPolicies owned by the user to be removed to the UserProfile of the current user (the user executing this code, such as DataCurator).

This form passes in the instance of UserProfile for the user to be deleted:

AllUsers removeAndCleanup: aUserProfile.

While this form passes in the userId of the user to be deleted, and includes a block to execute if no UserProfile with the given userId exists in AllUsers:

AllUsers removeAndCleanupUserWithId: ‘aUserId’
	ifAbsent: aBlock
Remove User, with ObjectSecurityPolicies going to another User

The following methods remove the user and reassign any GsObjectSecurityPolicies owned by that user to another specific UserProfile.

This form passes in the instance of UserProfile for the user to be deleted and the UserProfile to which to reassign the ObjectSecurityPolicies:

AllUsers removeAndCleanup: aUserProfile
	migrationSecurityPoliciesTo: anotherUserProfile.

While this form passes in the userId String of the user to be deleted and the userId String of the UserProfile to which to reassign the ObjectSecurityPolicies, and includes a block to execute if a UserProfile does not exist with either of the UserId Strings:

AllUsers removeAndCleanupUserWithId: ‘aUserId’
	migrationSecurityPoliciesUserWithId: ‘anotherUserId’
	ifAnyAbsent: aBlock

For example,

topaz 1> printit
AllUsers removeAndCleanup: (AllUsers userWithId: 'John')
	migrationSecurityPoliciesTo: (AllUsers userWithId: 'Ann')
System commitTransaction.

6.3  Administering Users

List Existing Users

Privileges required: None.

There is no direct method within GemStone Smalltalk to list only the names of existing accounts. The following example shows one way to obtain that information:

topaz 1 > run
(AllUsers collect: [:each | each userId ]) asArray.
%
#1 SystemUser
#2 DataCurator
#3 Nameless
#4 SymbolUser
#5 GcUser

Modifying the UserId

Privileges required: ChangeUserId.

Updating the userId requires resetting the password for that user. The new user ID and password will take effect when you commit the current transaction. The names of special system users cannot be changed.

To modify the user ID of a GemStone user, execute the following expression:

(AllUsers userWithId: 'theUserId')
	userId: 'newId'
	password:'newPassword'.

An error is raised if newId is the userId of an existing UserProfile.

Modifying Password

Users Changing Their Own Password

Privileges required: UserPassword.

In many cases, users set their own passwords and may be required to update them periodically. These users must be given the UserPassword privilege to do so, and use the method UserProfile >> oldPassword:newPassword: to update their password.

For example:

System myUserProfile
	oldPassword: 'oldPasswordString'
	newPassword: 'newPasswordString'.

Password choice is constrained by login security that is configured for the repository; see the discussion under Limiting Choice of Passwords.

The new password takes effect when you commit the current transaction.

A different method, requiring other privileges, is used by Administrators to update the password of another user.

Changing Another User’s Password

Privileges required: OtherPassword.

To modify the password of any GemStone user, execute the following expression.

(AllUsers userWithId: 'theUserId')
	password: 'newPasswordString' .

The new password takes effect when you commit the current transaction.

The password set by this method is not subject to the constraints described under Limiting Choice of Passwords, because this method can only be used by a user having the OtherPassword privilege. The password must not be the same as the UserId and must not be longer than 1024 characters.

Each password change of this type is noted in the GemStone security log, which currently is the Stone’s log file. The entry includes the userId of the session making the change but not the new password.

Modifying defaultObjectSecurityPolicy

Each security policy maintains access authorization for its owner, the world, and an unlimited number of groups. There are three levels of authorization: none, read (read-only), and write (which includes read permission).

Determining Who Is Authorized to Read or Write in an Object Security Policy

Privileges required: read authorization for the security policy with which this policy is associated, such as the DataCuratorObjectSecurityPolicy.

You can find out who is authorized to read or write objects in an security policy by sending it the message asString.For instance:

topaz 1> printit
PublishedObjectSecurityPolicy asString
%
anObjectSecurityPolicy, Number 6 in Repository SystemRepository,
Owner SystemUser write, Group Subscribers read, Group Publishers
write, World none
Changing the Authorization of an Object Security Policy

Privileges required: ObjectSecurityPolicyProtection or be the security policy’s owner, and write authorization to the DataCuratorObjectSecurityPolicy.

The new authorization will take effect for logins following the commit of the current transaction.

CAUTION
Do not attempt to change the authorization of SystemObjectSecurityPolicy.

To change the authorization for a security policy, execute any (or all) of the following expressions.

theObjectSecurityPolicy ownerAuthorization: #anAuthorizationSymbol.
 
theObjectSecurityPolicy worldAuthorization: #anAuthorizationSymbol.
 
theObjectSecurityPolicy group: 'aGroupString'
	authorization: #anAuthorizationSymbol.
 

NOTE
Exercise caution when changing the authorization for any security policy that a user may be using as his or her default or current security policy — whether or not the user owns the affected policy. If a user attempts to commit a transaction, but has created objects with a policy for which he or she no longer has write authorization, an error will be generated.

For example, to authorize the group Accounting to read (but not write) in user Eli’s default security policy, you could execute the following expression:

(AllUsers userWithId: 'Eli') defaultObjectSecurityPolicy
	group: 'Accounting'
	authorization: #read.

If the group 'Accounting' does not exist, GemStone will return an error. The discussion under Adding a User to a Group“explains how to create a new GemStone group.

Remove a Group from an Object Security Policy’s Authorization List

Privileges required: ObjectSecurityPolicyProtection, and write authorization for the security policy.

To remove a group from a security policy’s list of authorized groups, execute an expression similar to the following:

theSecurityPolicy group: 'aGroupString' authorization: #none
Change a User’s Default Object Security Policy

Privileges required: DefaultObjectSecurityPolicy, and write authorization to the DataCurator ObjectSecurityPolicy.

Changes to another user’s default security policy do not take effect until the next login.

To change a user’s default security policy, execute the following expression:

(AllUsers userWithId: 'theUserId') 
	defaultObjectSecurityPolicy: aNewSecurityPolicy
 

NOTE
If you change any user’s default security policy (including your own) to a security policy for which that user lacks write authorization, and you subsequently commit the transaction, the affected user will no longer be able to log in to GemStone.

Modifying Groups

Examining Group Memberships

No privileges are required for this operation.

To find out which groups a user belongs to, execute the following expression:

(AllUsers userWithId: 'theUserId') groups

This expression returns a Set of Strings indicating the groups to which the user belongs.

Adding a User to a Group

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

To add a user to a group, do the following:

('MarathonRunners' in: AllGroups)
	ifFalse: [AllGroups add: 'MarathonRunners' ].
(AllUsers userWithId: 'theUserId') addGroup: 'MarathonRunners'.

This expression adds the user to the group MarathonRunners by adding the group name to the list of groups maintained in the UserProfile. (This action takes effect when you commit the current transaction.)

If the group MarathonRunners did not previously exist, this expression creates it in AllGroups (the “master list” of all group names). See AllGroups for more information.

Removing a User from a Group

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

To remove a user from a group, execute an expression of the following form:

(AllUsers userWithId: 'theUserId') removeGroup: 'Sprinters'.

This expression removes the designated group from the list of groups to which the user belongs. This action will take effect when you commit the current transaction. For more information about groups, see the Security chapter of the Programming Guide.

Listing Members of a Group

No privileges are required for this operation.

To list all members of a user group, execute an expression of the following form:

AllUsers membersOfGroup: aString

This expression returns an IdentitySet containing the userId for each member of the group.

Removing a Group

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

To remove a user group from the global object AllGroups, first remove each member from the group, then remove the group itself, using the expression

AllGroups removeGroup: aGroupString. 

For example, to remove a group named MarathonRunners:

topaz 1> printit
  "Remove each member from the group "
(AllUsers usersInGroup: 'MarathonRunners') do:
        [:aUserProfile| aUserProfile removeGroup: theGroup]. 
  "Now remove the group itself "
AllGroups remove: 'MarathonRunners'.
System commitTransaction
%

Modifying Privileges

Examining a User’s Privileges

No privileges are required for this operation.

GemStone provides messages that allow you to determine which privileged methods a GemStone user may execute, and to change the privileges of any user. Naturally, you need the appropriate privileges to use those methods.

To find out which privileged methods a given user is permitted to execute, send the following message to the desired user’s UserProfile:

(AllUsers userWithId: 'theUserId') privileges

This message returns an Array of Strings. Each String in the Array corresponds to one of the user’s privileges. Table 6.1 lists the Smalltalk methods that correspond to each privilege.

Adding a Privilege

Privileges required: OtherPassword and write authorization to the ObjectSecurityPolicy of the user’s UserProfile.

The new privileges will take effect when you commit the current transaction.

To add to a user’s existing privileges, execute the following expression:

(AllUsers userWithId: 'theUserId') addPrivilege: aPrivilegeString .

Here’s an example that assigns three new privileges to user Bob:

topaz 1> printit
(AllUsers userWithId: 'Bob')
	addPrivilege: 'SystemControl';
	addPrivilege: 'SessionAccess';
	addPrivilege: 'UserPassword' .
System commitTransaction
%
Revoking a Privilege

Privileges required: OtherPassword and write authorization to the security policy of the user’s UserProfile.

The privileges will be revoked when you commit the current transaction.

To revoke one (or more) of a user’s existing privileges, execute the following expression:

(AllUsers userWithId: 'theUserId') 	deletePrivilege: aPrivilegeString.

The following example revokes two of user Jane’s privileges:

topaz 1> printit
(AllUsers userWithId: 'Jane')
	deletePrivilege: 'SystemControl';
	deletePrivilege: 'SessionAccess';
System commitTransaction
%
Reassigning All Privileges

Privileges required: OtherPassword and write authorization to the security policy of the user’s UserProfile.

The new privileges will take effect when you commit the current transaction.

To redefine the full set of a user’s privileges, perhaps adding some and revoking others, execute the following expression:

(AllUsers userWithId: theUserId) privileges: anArrayOfStrings 

This expression supersedes any previous privilege assignments. After the change is committed, only those privileges listed in the expression are valid for the user. Any privileges that were previously valid, but are not listed, are revoked.

For example:

topaz 1> printit
(AllUsers userWithId: 'Sam') privileges: 
	#( 'UserPassword' ) .
System commitTransaction
%

Modifying SymbolLists

Adding a SymbolDictionary to Your Own Symbol List

Privileges required: CodeModification.

You can add a dictionary to a symbol list by sending the message UserProfile>>insertDictionary: aSymbolDictionary at: anIndex. The change does not affect the transient copy of the symbol list that is used by another currently logged in session until that session commits or aborts.

This example inserts dictionary NewDict (which already exists in the Published dictionary) into the user’s own symbol list:

topaz 1> printit
System myUserProfile
	insertDictionary: NewDict at: 2.
%

Inserting the new dictionary at index 2, as in the example, places it between the UserGlobals and the Globals dictionaries in the search order. Because symbol resolution depends on the order of dictionaries in a user’s symbol list, the index used in this example may not be appropriate for all situations.

Adding a SymbolDictionary to Someone Else’s Symbol List

Privileges required: OtherPassword and write permission to the security policy of the other user’s SymbolDictionary.

This example inserts dictionary NewDict (which already exists in the Published dictionary) into user Jerry’s symbol list:

topaz 1> printit
(AllUsers userWithId: 'Jerry')
	insertDictionary: NewDict at: 7.
System commitTransaction
%
Removing a SymbolDictionary from Your Own Symbol List

Privileges required: CodeModification.

You can remove a dictionary from a symbol list by sending the message UserProfile>>removeDictionary: aSymbolDictionary.

The change does not affect the transient copy of the symbol list that is used by another currently logged in session, until that session commits or aborts.

This example removes dictionary OldDict from the user’s own symbol list:

topaz 1> printit
System myUserProfile
	removeDictionary: OldDict.
System commitTransaction
%
Removing a SymbolDictionary from Someone Else’s Symbol List

Privileges required: OtherPassword and write permission to the security policy of the other user’s SymbolDictionary,

This example removes dictionary OldDict from user Jerry’s symbol list:

topaz 1> printit
(AllUsers userWithId: 'Jerry')
	removeDictionary: OldDict.
System commitTransaction
%

Disable and Enable User Logins

A disabled account cannot log in. Disabling an account consists of setting its password to a non-enterable character. Once an account is disabled, a new password must be assigned for that account by an administrator, before the account can log in again.

Disable an Account Explicitly

Privileges required: OtherPassword. Logins cannot be disabled for special system accounts.

Users can be disabled using the method disable or disableWithReason:. This prevents the user from logging in, but does not affect current login sessions.

For example:

topaz 1> printit
(AllUsers userWithId: 'Sam') 
	disableWithReason: 'on Sabbatical'.
System commitTransaction
%
Re-enable an Account

Privileges required: OtherPassword.

To re-enable a user account, an administrative user must login and reset the users’s password. For example, to reset Sam’s password and require him to change his password the first time he logs in:

topaz 1> printit
(AllUsers userWithId: 'Sam') 
	password: 'AaBbCc';
	loginsAllowedBeforeExpiration: 1.
System commitTransaction
%
Find Out Which Accounts Have Been Disabled

Privileges required: OtherPassword.

The message AllUsers findDisabledUsers returns a SortedCollection of UserProfiles that are disabled explicitly or by one of the security precautions discussed in this chapter:

  • The password expired (through aging or a login limit).
  • The account remained inactive.
  • There were repeated password failures.

For example:

topaz 1> level 1
topaz 1> printit
AllUsers findDisabledUsers 
  collect: [:aUser | aUser userId ] .
%
an Array
  #1 qa2
  #2 qa3

DataCurator or another user with the OtherPassword privilege can reactivate an account by giving it a new password, as described under Changing Another User’s Password.

Check If an Account Is Disabled

Privileges required: OtherPassword.

You can check if a particular account is disabled by sending the message isDisabled to the account’s UserProfile. The method returns either True or False. This example inquires about account qa2:

topaz 1> printit
(AllUsers userWithId: 'qa2') isDisabled
%
true
Find Out Why an Account Was Disabled

Privileges required: OtherPassword.

You can find out why a particular account was disabled by sending the message reasonForDisabledAccount to the account’s UserProfile. This example inquires about account qa2:

topaz 1> printit
(AllUsers userWithId: 'qa2') reasonForDisabledAccount
%
LoginsWithSamePassword

If the account was disabled using the method disableWithReason:, this method will return that argument. Otherwise if the account was disabled by login security, it will return one of these Strings: 'PasswordAgeLimit', 'StaleAccount', 'LoginsWithSamePassword', or 'LoginsWithInvalidPassword'.

Disable and Enable Commits by User

Commits can be disabled for particular users to ensure “read only” access to the GemStone repository. These users can still log in and view data for which they have read or write authorization, and can modify objects, but they cannot commit and make any changes permanent.

Disable Commits

Privileges required: OtherPassword. Commits cannot be disabled for special system users.

To disable commits for a user, execute the following expression:

(AllUsers userWithId: theUserId) disableCommits

This expression disables any commits for the given user, beginning with the next login of the user after the session making this change commits. If this user is currently logged in, It does not affect the user’s transaction, if any.

For example:

topaz 1> printit
(AllUsers userWithId: 'Sam') disableCommits.
System commitTransaction
%
Re-enable Commits for a User

Privileges required: OtherPassword.

To enable commits for a user, execute the following expression:

(AllUsers userWithId: theUserId) enableCommits

This expression enables commits for the given user, beginning with the next login after the session making this change commits.

Check If a User Can Commit

Privileges required: OtherPassword.

To test if commits have been disabled for a user account, execute the following expression:

(AllUsers userWithId: theUserId) isReadOnly

6.4  Password Authentication

When a user wants to log in to the GemStone repository, their login must be authenticated: they must present a password that is matched against stored information for their UserId, to verify that they are authorized to log in. This authentication can be done entirely within GemStone, or GemStone can use UNIX or LDAP to perform the authentication.

Performing the authentication entirely with GemStone – Gemstone authentication – is the initial default for all users. Other authentication schemes can be configured for individual UserProfiles, although special system users will always use GemStone authentication. The repository may contain UserProfiles using a mix of authentication schemes.

After the authentication scheme is modified for a user and the change is committed, it will take effect the next time the user logs in. Existing logins are not affected.

In addition to GemStone user login authentication, users may need to provide OS userId and password authentication, to execute processes at the OS level. This depends on how the system is configured; see Chapter 3 for details interprocess security and host authentication.

GemStone Authentication

Privileges required: OtherPassword

GemStone authentication is the default authentication, using the UserId and password store with the UserProfile of the account. In older versions, this was the only way of authentication within GemStone, and must still always be used by the special system users - SystemUser, DataCurator, GcUser, SymbolUser, and Nameless.

topaz 1> printit
(AllUsers userWithId: 'Mary') enableGemStoneAuthentication.
System commitTransaction.
%

UNIX Authentication

Privileges required: OtherPassword

When UNIX account authentication is enabled for a user, they will enter their UNIX account password instead of their GemStone password. Password management for this user then becomes the Unix password management; sending messages to change the GemStone password are not useful, and these accounts are not subject to GemStone’s password aging mechanisms.

UNIX authentication uses PAM (pluggable authentication module) , and PAM must be configured on the system in order to use UNIX Authentication. Login will look for a module gemstone.gem. See the Installation Guide for your server platform for details.

The GemStone userId may be the same as the UNIX userId, or they may be different. When you enable UNIX authentication for an account, you may specify the UNIX userId associated with the GemStone UserProfile and this will be used for authentication. Using nil means to use the GemStone userId as the UNIX userId.

(AllUsers userWithId: GemStoneUserId) 
	enableUnixAuthenticationWithAlias: UNIXUserIdOrNil

for example, if Mary’s UNIX userId is msmith, you can use the first form if her GemStone userId is Mary. If her GemStone userId is also msmith you leave the argument nil.

topaz 1> printit
(AllUsers userWithId: 'Mary') 
	enableUnixAuthenticationWithAlias: 'msmith'.
System commitTransaction.
%
topaz 1> printit
(AllUsers userWithId: 'msmith') 
	enableUnixAuthenticationWithAlias: nil.
System commitTransaction.
%

To verify that the UNIX userId exists, execute:

System unixUserIdExists: UNIXUserId

LDAP Authentication

Privileges required: OtherPassword

An LDAP (Lightweight Directory Access Protocol) server consolidates and centralizes user authentication, and can be used to authenticate logins to the GemStone server. UNIX authentication, which uses PAM, may ultimately use LDAP if PAM is configured to use LDAP. When you configure GemStone to use LDAP authentication, it goes to LDAP directly, rather than using PAM.

To use LDAP for GemStone authentication, you must have an LDAP server available. When a UserProfile has been configured for LDAP authentication, on login, GemStone performs an LDAP bind to authenticate the userId.

In most cases, the LDAP bind requires a Distinguished Name (DN), which is the unique identifier for an entry. The DN includes both the userId and domain information, e.g. 'uid=msmith,ou=employees,dc=somecompany,dc=com'. GemStone composes the DN based on the arguments provided when you configure LDAP authentication.

To configure a user to use LDAP for authentication, use the following method:

(AllUsers userWithId: GemStoneUserId) 
	enableLDAPAuthenticationWithAlias: LDAPUserIdOrNil
	baseDn: baseDn
	filterDn: filterDn
UserId or Alias

As with UNIX authentication, if the LDAP userId is the same as the GemStone Id, you may pass in nil for the argument UNIXUserIdOrNil; otherwise, pass in the LDAP userId. The user will use their GemStone userId and the password for their LDAP account to log in.

Fully Qualified DN

You can configure the LDAP authentication for a particular user with the specific DN, with ‘%s’ replacing the userId, for the baseDn: argument. In this case you should pass in nil for the filterDn: argument, which will disable the query.

For example, to configure authentication to use a specific fully qualified DN:

topaz 1> printit
(AllUsers userWithId: 'Mary') 
	enableLDAPAuthenticationWithAlias: 'msmith'
	baseDn: 'uid=%s,ou=employees,dc=somecompany,dc=com' 
	filterDn: nil.
System commitTransaction.
%
Search for DN

You can also configure authentication to include the base domain information in the baseDn: argument, and include a filter in the filterDn: argument. The filter must contain ‘%s’ in the position for the userId. GemStone will perform a query to get the full DN given the base and filter information.

The base and filter information is provided at the time the authentication is configured, not at login time, so a search option is particularly useful if the LDAP structure is likely to be modified.

To configure authentication to do a search for the given user:

topaz 1> printit
(AllUsers userWithId: 'Mary') 
	enableLDAPAuthenticationWithAlias: 'msmith'
	baseDn: 'dc=somecompany,dc=com'
	filterDn: '(uid=%s)'.
System commitTransaction.
%

LDAP authentication without anonymous binds

By default, GemStone login authentication requires that the LDAP server allow anonymous binds. For installations that disallow anonymous binds, you may configure instances of LdapDirectoryServer with the information required for authenticated binds.

AllLdapDirectoryServers is a global collection of instance of LdapDirectoryServer. Each instance of LdapDirectoryServer contains authentication information for an particular LDAP server.

During login, If there are existing instances of LdapDirectoryServer in AllLdapDirectoryServers, then these are used, in order; normally this would include a primary LDAP server and an alternate. If and only if the bind to the first LDAP server fails, the second is tried, and so on. If no binds are successful, then the login will fail.

If AllLdapDirectoryServers is empty, then login will use anonymous bind.

To create an LdapDirectoryServer, and add it to the global list, use the following method:

LdapDirectoryServer class >> newWithUri: uri bindDN: aBindDn
	password: password

If LDAP authentication is setup, using

enableLDAPAuthenticationWithAlias: aString baseDn: baseDn filterDn: filterDn

then either anonymous or authenticated binds are performed to authenticate the user password.

For example, to set up a user to use LDAP authentication, and setup a single LDAP server to authenticate binds, the following code would be executed.

aUserProfile 
	enableLDAPAuthenticationWithAlias: nil
	baseDn: 'ou=Users,dc=gemtalksystems,dc=com'
	filterDn: '(uid=%s)' .
 
LdapDirectoryServer 
	newWithUri: 'ldaps://ldap.gemtalksystems.com'
	bindDN: 'uid=bindUser,ou=Users,dc=gemtalksystems,dc=com'
	password: 'swordfish' .

Users are individually configured to use LDAP authentication, but LDAP directories are configured for the repository as a whole, so this step is only done once. All users use the same bind authentication, though they use their own individual passwords to perform the actual account authentication.

Note that the instances of LdapDirectoryServer store the encrypted bind password. This is not the actual user password to login to GemStone; this bind password allows you to perform the user password authentication.

To determine if a particular LdapDirectoryServer exists, use the following method, which returns either the LdapDirectoryServer or nil if it is not found:

LdapDirectoryServer >>findServerWithUri: aUriString

to remove an LdapDirectoryServer, use the method:

LdapDirectoryServer >> removeServerWithUri: aUriString

Validating passwords

Once logged in, you can perform an validation call to the LDAP server that passes in the LDAP authentication as well as the user authentication, using this method:

System class >> validatePasswordUsingLdapServers:baseDn:
	filterDn:userId:password:bindDn:bindPassword: 

The additional bind arguments should be nil if authenticating in explicit mode, or with anonymous bind. For example:

Explicit mode
System validatePasswordUsingLdapServers: 
		(Array with: 'ldaps://myldap.mydomain.com')
	baseDn: 'uid=%s,ou=Users,dc=mycompany,dc=com' 
	filterDn: nil
	userId: 'MyUserId' password: 'swordfish' 
	bindDn: nil bindPassword: nil
Search mode with anonymous bind
System validatePasswordUsingLdapServers: 
		(Array with: 'ldaps://myldap.mydomain.com')
	baseDn: 'ou=Users,dc=mycompany,dc=com' 
	filterDn: '(uid=%s)'
	userId: 'MyUserId' password: 'swordfish' 
	bindDn: nil bindPassword: nil
Search mode with authenticated bind
System validatePasswordUsingLdapServers: 
		(Array with: 'ldaps://myldap.mydomain.com')
	baseDn: 'ou=Users,dc=mycompany,dc=com' 
	filterDn: '(uid=%s)'
	userId: 'MyUserId' password: 'swordfish' 
	bindDn: 'LdapBindUser' bindPassword: 'LdapBindPassword'

For further details, see the method comments in the image.

Determining an Account’s Authentication Scheme

Privileges required: OtherPassword

Your repository may contain a mix of authentication schemes, with some users (at least certainly the special system accounts) using GemStone authentication, others authenticating using UNIX accounts, and still other using LDAP.

You can determine what scheme an account is using by sending authenticationScheme. This will return the symbol #GemStone, #UNIX, or #LDAP. For example,

topaz 1> printit
(AllUsers userWithId: 'DataCurator') authenticationScheme.
%
GemStone

You may also send messages to check for specific schemes. The following methods return true or false.

(AllUsers userWithId: userId) authenticationSchemeIsUNIX
 
(AllUsers userWithId: userId) authenticationSchemeIsLDAP
 
(AllUsers userWithId: userId) authenticationSchemeIsGemStone

6.5  Configuring GemStone Login Security

GemStone provides several login security features. You can:

In all cases, the password must not be the same as the UserId and must not be longer than 1024 characters.

Additional methods let you determine which accounts have been disabled by one of these security features and why a particular account was disabled.

CAUTION
GemStone records certain administrative changes to these security features in the Stone log. You may want to restrict access to that file.

The special system users - SystemUser, DataCurator, SymbolUser, GcUser, and Nameless - are never disabled by the security features.

Limiting Choice of Passwords

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

You can constrain a user’s choice of passwords in terms of pattern (such as the number of characters that repeat). Independently, you can establish a list of words that are disallowed as passwords, and you can keep a user from choosing the same password more than once.

The constraints described here apply only when a user changes his or her own password by using the message UserProfile>>oldPassword:newPassword: and only to password changes after the constraint is committed to the repository. That is, the constraints (other than the prohibition of userId as the password) do not apply to changing a users’s user’s password using the password: method (which requires OtherPassword privilege), and they do not invalidate existing passwords.

Table 6.3 lists the messages that you can use to set pattern constraints. You send these messages to the global object AllUsers. For example, to set the minimum password length to six characters, do this:

topaz 1> printit
AllUsers minPasswordSize: 6.
System commitTransaction
%

The default setting in all cases is 0, which means there is no constraint on the pattern.
Any user can inquire about the current setting of a password pattern constraint by sending its corresponding Accessing message (that is, without the colon or argument shown in Table 6.3).

Table 6.3 Ways to Constrain the Password Pattern

Message to AllUsers

Comments

minPasswordSize: aPositiveInteger

Sets the minimum number of characters in a new password; 0 means no constraint.

maxPasswordSize: aPositiveInteger

Sets the maximum number of characters in a new password; 0 disables the constraint. (The password String itself must not be longer than 1024 characters.)

maxRepeatingChars: aPositiveInteger

Sets the maximum number of adjacent characters that can have the same value; for example, 1 allows 'aba' but not 'aa'. 0 means no constraint.

maxConsecutiveChars: aPositiveInteger

Sets the maximum number of adjacent characters that can be an ascending or descending sequence, such as '123' or 'zyx' based on a case-sensitive comparison. 0 means no constraint.

maxCharsOfSameType: aPositiveInteger

Sets the maximum number of adjacent characters that can be of the same type (alpha, numeric, or special); for example, 3 allows 'abc4de' but not 'abcde'. 0 means no constraint.

passwordRequiresUppercase: aBoolean

Set the requirement that the password contain at least one uppercase character. false means no constraint.

passwordRequiresLowercase: aBoolean

Set the requirement that the password contain at least one lowercase character. false means no constraint.

passwordRequiresSymbol: aBoolean

Set the requirement that the password contain at least one character that is not alphanumeric. false means no constraint.

passwordRequiresDigit: aBoolean

Set the requirement that the password contain at least one digit. false means no constraint.

For example, to determine the current minimum size for a password:

topaz 1> printit
AllUsers minPasswordSize 
%
6

Disallowing Particular Passwords

Privileges required: OtherPassword and write authorization to the DataCuratorObjectSecurityPolicy.

You can create a list of disallowed passwords by adding Strings to the AllUsers instance variable disallowedPasswords. Any messages understood by class Set can be used. For instance:

topaz 1> printit
(AllUsers disallowedPasswords) 
  addAll: #( 'Mother' 'apple_pie' ) .
System commitTransaction
%

The default is an empty set.

Additions to this list affect only new passwords requested after the additions are committed; that is, additions do not invalidate existing passwords. If a user attempts to change that account’s password to one of the Strings in disallowedPasswords, a SecurityError is signaled.

Any user can examine the current list of globally disallowed passwords by sending the message AllUsers disallowedPasswords.

Disallowing Reuse of Passwords

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

You can prevent each user from choosing the same password more than once by setting AllUsers disallowUsedPasswords to true. By default, disallowUsedPasswords is false.

When reuse of passwords is disallowed, GemStone maintains a separate encrypted set of old passwords for each user. Each time a user invokes oldPassword:newPassword:, the new password is checked against the prior passwords for that account. If the new password matches a prior one, a SecurityError is signaled.

Disallow All Previously Used Passwords

To disallow password reuse, use the method AllUsers disallowUsedPasswords: aBoolean.

For example:

topaz 1> printit
AllUsers disallowUsedPasswords: true .
System commitTransaction
%
Disallow a Specific Number of Previous Passwords

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy.

To disallow a fixed number of previously-used password, but allow earlier passwords, use the following:

AllUsers numberOfDisallowedPasswords: anInteger

anInteger must be a number between 0 and 65535; 0 means that the user may not reuse any previously-used passwords. The limit on the number of disallowed passwords has no effect if disallowUsedPasswords is false.

For example,

topaz 1> printit
AllUsers disallowUsedPasswords: true.
AllUsers numberOfDisallowedPasswords: 10.
System commitTransaction
%
Clearing a User’s Disallowed Old Passwords

Privileges required: OtherPassword.

You can clear the set of old passwords so that they can be reused by sending the message clearOldPasswords to that user’s UserProfile. As mentioned above, this set is maintained for each user when the AllUsers instance variable disallowUsedPasswords is set to true. The following example clears the remembered passwords for Mary’s account:

topaz 1> printit
(AllUsers userWithId: 'Mary') clearOldPasswords.
System commitTransaction
%

Password Aging – Require Periodic Password Changes

You can configure your system so users must change their password periodically. This can be configured at the repository-wide level, or for individual users. Note that if you are not using GemStone login authorization, password aging does not apply.

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy. The special GemStone accounts are not disabled by password aging.

Repository-Wide Password Aging

You can require users to change their password periodically by setting up a password age limit. This can be set for all users in the repository, and can be overridden for specific individual users.

To set a password page limit for all users in the repository, use the method UserProfileSet>>passwordAgeLimit:numberOfHours.

For example, to set the limit to 120 days:

topaz 1> printit
AllUsers passwordAgeLimit: 120 * 24 .
System commitTransaction
%

The passwordAgeLimit is added to the time the password was last changed to determine when the password will expire. A setting of 0 (the default) disables password aging.

Each time this method is invoked, the action is recorded in the Stone’s log.

If a user does not change the account’s password within the specified interval, the account is disabled, and attempts to log in result in an error.

DataCurator or another user with the OtherPassword privilege can reactivate the disabled account by giving it a new password; see Re-enable an Account for details.

Password Age Limits for Individual Users

To override the repository-wide setting for password aging, you can set a password age limit for a specific user, by using the method UserProfile>>passwordAgeLimit:numberOfHours.

For example, to set the limit for a Mary to 5 days:

topaz 1> printit
(AllUsers userWithId: 'Mary') passwordAgeLimit: 5 * 24.
System commitTransaction
%

If repository-wide password aging is enabled, you can also override this for individual users, such as managers or administrators, either by setting the passwordAgeLimit for that UserProfile to 0, or by executing setPasswordNeverExpires: with a true argument.

For example, to prevent the password used by batch jobs from expiring, execute:

topaz 1> printit
(AllUsers userWithId: 'BatchUser') 
	setPasswordNeverExpires: true.
System commitTransaction
%
Repository-Wide Password Expiration Warning

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy. This does not apply to system users, and has no effect if password aging is not enabled.

You can provide an automatic warning to users repository-wide whose password is about to expire by sending the message UserProfileSet>>passwordAgeWarning:numberOfHours. For example, to warn users who log in within five days of the time their password will expire, do this:

AllUsers passwordAgeWarning: 5 * 24.

Logins within numberOfHours prior to expiration cause a SecurityError to be signaled.

Per-User Password Expiration Warning

You can provide a similar automatic warning to specific user using UserProfile>>passwordAgeWarning:numberOfHours.

For example, to warn Mary within three days of the time her password will expire, do this:

topaz 1> printit
(AllUsers userWithId: 'Mary') passwordAgeWarning: 3 * 24.
System commitTransaction
%
Finding Accounts with Password About to Expire

Privileges required: OtherPassword.

You can find out which accounts have a password within the warning period set by passwordAgeWarning:. To do this, send the message findProfilesWithAgingPassword to AllUsers. For example:

topaz 1> printit
AllUsers findProfilesWithAgingPassword 
  collect: [ :u | u userId] .
%
an OrderedCollection
#1 qa1
#2 qa2
#3 qa3
Finding Out When a Password Was Changed

Privileges required: OtherPassword.

You can find out the last time the password was changed for a particular userId by sending the message lastPasswordChange to that account’s UserProfile. This example converts the DateTime returned to a particular pattern based on MM/DD/YY:

topaz 1> printit
(AllUsers userWithId: 'qa2') lastPasswordChange US12HrFormat
%
04/06/15 11:28 AM

Account Aging – Disable Inactive Accounts

You can configure your system so users must log in periodically, by disabling accounts for which there has been no login for a specified length of time. This can be configured at the repository-wide level, or for individual users.

Privileges required: OtherPassword and write authorization to DataCuratorObjectSecurityPolicy. The special GemStone accounts are not disabled by stale account aging.

Repository-Wide Stale Account Aging

To do this, send the message staleAccountAgeLimit: numberOfHours to AllUsers. This example disables accounts when they have not logged in for 30 days:

AllUsers staleAccountAgeLimit: 30 * 24.

Each time this method is invoked, the action is recorded in the Stone log.

A setting of 0 (the default) disables account aging.

DataCurator or another user with the OtherPassword privilege can reactivate the disabled account by giving it a new password; see Re-enable an Account for details.

Per-User Stale Account Aging

You can override the repository-wide setting for account aging for a specific user using UserProfile>>staleAccountAgeLimit: numberOfHours.

For example, for the ‘Auditor’ account who may log in less frequently, you can set up a 180-day stale account age limit. This will override a repository-wide 30 day setting.

topaz 1> printit
(AllUsers userWithId: 'Auditor') 
	staleAccountAgeLimit: 180 * 24.
System commitTransaction
%
Finding Out When an Account Last Logged In

Privileges required: OtherPassword.

If at least one age limit applies to an account, you find out when that account last logged in by sending the message lastLoginTime to that account’s UserProfile. For example:

topaz 1> printit
(AllUsers userWithId: 'Mary') lastLoginTime US12HrFormat
%
04/06/15 01:40 PM

The time of the last login is maintained only if loginsAllowedBeforeExpiration is set in that UserProfile or if at least one of these instance variables is set in AllUsers or the specific UserProfile: passwordAgeLimit, passwordAgeWarning, or staleAccountAgeLimit. If none of these features are enabled, the lastLoginTime may be nil, the time of the account creation, or a time representing a login during an earlier period when one of these features was enabled. This is also true if a feature that enables lastLoginTime recording has been enabled on more recently than the last login of the user.

The data curator may explicitly set the time of the last login, using the method UserProfile >> lastLoginTime:.

Enabling Account Aging and lastLoginTime

Privileges required: OtherPassword.

The time of the last login is recorded for a UserProfile only if loginsAllowedBeforeExpiration is set in that UserProfile or if at least one of these instance variables is set in AllUsers or the specific UserProfile: passwordAgeLimit, passwordAgeWarning, or staleAccountAgeLimit. This is to avoid the commit during login, which is required to record the lastLoginTime.

As a result, you should use caution in enabling account aging on existing repositories. Enabling account aging may result in user accounts being disabled.

To avoid this, when you enable account aging, you can set the lastLoginTime to the current date, or to nil, for all affected UserProfiles. A nil setting disables account aging checks, allowing the aging period to being with the next login.

topaz 1> printit
AllUsers passwordAgeLimit: 120 * 24.
AllUsers do: [:aUserProfile |
	aUserProfile lastLoginTime: DateTime now.
	].
System commitTransaction.
%

Limit Logins Until Password Is Changed

Privileges required: OtherPassword.

When you assign a password to an account, you can make the password temporary by limiting the number of times it can be used. This limitation applies only to a specific account, that is, to the UserProfile that is the receiver of the message. It is intended for use with a new or reactivated account as a means of ensuring that the user changes the password. For example, the following limits the account “Mary” to two more logins under the current password:

(AllUsers userWithId: 'Mary') 
	loginsAllowedBeforeExpiration: 2.

A setting of 0 (the default) disables this feature.

The limit remains in effect until the user changes the password. Once the password is changed, the limit for that account is set to 0. The password will not expire again unless a new limit is set by repeating loginsAllowedBeforeExpiration:.

If the limit is exceeded before the password is changed, the system disables the account. DataCurator or another user with the OtherPassword privilege can reactivate the disabled account by giving it a new password; see Re-enable an Account for details.

The special user accounts are not disabled by this mechanism.

Limit Concurrent Sessions by a Particular UserId

Privileges required: OtherPassword.

You can limit the number of concurrent sessions logged in under a particular userId by sending the message activeUserIdLimit: aPositiveInteger to the UserProfile for that account.

For example, the following limits the userId “qa2” to four concurrent sessions:

(AllUsers userWithId: 'qa2') activeUserIdLimit: 4.

A setting of 0 (the default) disables this feature.

If a user attempts to log in when the maximum number of sessions for that userId are already logged in, the login is denied and a SecurityError is signalled.

Limit Login Failures

Record Login Failures

The Stone repository monitor keeps track of login failures (incorrect passwords) for each account and can write that information to the Stone’s log.

By default, messages are logged when the same account fails login attempts 10 or more times within ten minutes. You can change the default limits by setting the STN_LOG_LOGIN_FAILURE_LIMIT and STN_LOG_LOGIN_FAILURE_TIME_LIMIT configuration options.

The log message gives the following information:

---Fri 1 May 2015 09:39:40 PDT ---
    GemStone user Mary has failed on 10 attempt(s) 
       to log in within 1 minute(s).
    The last attempt was from user account writer1 on hostname docs.
Disabling Further Login Attempts

If login failures continue, the Stone repository monitor can disable the account. By default, the account is disabled when the number of failures exceeds 15 within 15 minutes. You can change the default limits by setting the STN_DISABLE_LOGIN_FAILURE_LIMIT and (STN_DISABLE_LOGIN_FAILURE_TIME_LIMIT configuration options.

Subsequent attempts to login as that account result in the following error message:

Login failed:  the GemStone userId/password combination is invalid
or expired.

The special user accounts are not disabled by login failures.

DataCurator or another user with the OtherPassword privilege can reactivate the disabled account by giving it a new password; see Re-enable an Account for details.

6.6  Tracking User Logins

Login logging

It is sometimes useful to keep a log recording when each session logs in and out. You can configure your system to record login/logout events for each session in the repository. Other related operations, such as Stone startup and shutdown, are also recorded.

Tracking login/logout events is disabled by default. It is enabled by setting the STN_LOGIN_LOG_ENABLED configuration option to True in the configuration file used by the Stone, prior to Stone startup.

Once this is enabled for the repository, by default, all sessions that login to the stone will have logins and logouts logged. You can specify specific UserProfiles that will not have events logged, to avoid having the log cluttered with system logins. This is done with the method UserProfile >> disableLoginLogging. After this is executed, that UserProfile will not have logins or logouts recorded in the log file.

Logins and logouts are recorded to a text file named stoneName_login.log, in the same directory as the Stone log. Each log entry is on an individual line, with the following fields:

TimestampString TimeStampSeconds EventKind UserName SessionId ProcessId RealUserID EffectiveUserID HostName GemIPAddress ClientIPAddress NumCommits

For example:

"10/03/2013 16:42:48.720" 1380843768 STARTUP Stone 0 15270 631 631
kata.gemtalksystems.com 204.45.122.94 0.0.0.0 0
"10/03/2013 16:43:13.017" 1380843793 LOGIN DataCurator 3 15317 631
631 kata.gemtalksystems.com 204.45.122.94 10.94.141.15 0
"10/03/2013 16:43:23.488" 1380843803 SHUTDOWN Stone 0 15270 631
631 kata.gemtalksystems.com 204.45.122.94 0.0.0.0 0

Login Hook

The loginHook is an optional feature that allows you to specify code to be executed each time a specific userProfile logs in.

The argument to the method UserProfile >> loginHook: specifies either a symbol, which must be a unary selector of an instance method that was added to UserProfile, or a zero-argument block. When the userProfile logs in, if the loginHook: is not nil, than the method associated with the selector, or the block, is executed.

For example:

topaz 1> printit
(AllUsers userWithId: 'Mary')
	loginHook: [MyApplicationLogger logLogin: 'Mary']
System commitTransaction.
%

Previous chapter

Next chapter