8. 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 describes how users can be configured with the appropriate privileges and object security policies. To understand how GsObjectSecurityPolicies can be used to control access to data, see the chapter “Object Security and Authorization” in the Programming Guide.

8.1  GemStone Users

This section provides background information about how GemStone stores user accounts, predefined system users, groups of users, and users’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.

Authentication Scheme

How this user is authenticated; #GemStone, #UNIX, #LDAP, #SingleSignOn, or #X509.

Password

The GemStone-specific password (an InvariantString) to use to validate logins for #GemStone authentication. 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

Encoding for a logical 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 is used to resolve references to Classes and other Globals for this user.

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 is not restricted by any 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.

SystemUser can only be configured to use GemStone authentication and cannot be disabled.

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, but unlike SystemUser, does not have the ability to read and write all GsObjectSecurityPolicies.

DataCurator can be configured to use GemStone or SingleSignOn authentication, but not LDAP or UNIX, and cannot be disabled.

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. The only reason to login as GcUser is to examine or update configuration parameters stored in GcUser’s UserGlobals, although this is done more conveniently by executing code.

GcUser can be configured to use GemStone or SingleSignOn authentication, but not LDAP or UNIX, and cannot be disabled.

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.

HostAgentUser

The HostAgentUser account is a special account for use only by X509-Secured GemStone processes. Do not use this account or change except as directed by GemStone Engineering.

UserProfile Data

Each user profile contains important information about the account, which may be explicitly specified during instance creation or rely on default values. This information may also be updated during the course of time for the UserProfile.

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, and characters in the byte range (up to codePoint 255) are allowed.

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

Authentication Scheme

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, LDAP, or Kerberos to perform the authentication.

There is another authentication, X509, which requires a different login infrastructure; this is not described in detail here, see the GemStone/S 64 Bit X509-Secured GemStone System Administration Guide for more information. Users who are configured with other authentication schemes can both login using traditional login, and login using X509-secured GemStone. Users configured with X509 authentication are restricted to only logging in using X509-secured GemStone

Performing the authentication entirely within GemStone – Gemstone authentication – is the initial default for all users. Other authentication schemes can be configured for individual UserProfiles, though there are restrictions for system accounts. 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 authentication for the GemStone UserProfile, users may need to authenticate the UNIX username, before the NetLDI will start the Gem process for an RPC login. This depends on how the system is configured; see Chapter 4 for details on interprocess security and host authentication.

Changing authentication, or inquiring about authentication, requires #OtherPassword privilege.

Setting up GemStone and other authentication schemes is described in more detail starting here.

Determining an Account’s Authentication Scheme

Your repository may contain a mix of authentication schemes, with some users (such as SystemUser) using GemStone authentication, others authenticating using UNIX, LDAP, etc. You can determine what scheme an account is using by sending authenticationScheme. This will return the symbol #GemStone, #UNIX, #LDAP, #SingleSignOn, or #X509. For example,

(AllUsers userWithId: 'DataCurator') authenticationScheme.
%
GemStone

Password

Passwords for GemStone authentication must be:

  • invariant Strings
  • not empty
  • be different than the User Id
  • no longer than 1024 characters
  • contain only Characters with codePoints under 256

When each UserProfile is created, the initial password is defined and the account starts off using GemStone authentication. If the UserProfile is modified to use another authentication scheme, the initial password is discarded. For GemStone authentication, the password supplied in the login parameters is verified against this password.

GemStone authentication provides a number of controls on passwords, which apply to changing passwords and constraints on choice of password.

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.

When creating a user, you can use methods that specify that a new instance of GsObjectSecurityPolicy should be created and assigned as the default for the new user. Otherwise, you must use an already committed instance of GsObjectSecurityPolicy, either existing or newly created, or nil.

If the defaultObjectSecurityPolicy for a user is not nil, the user MUST have write authorization to this security policy; otherwise, this user will not be able to log in.

To modify your own defaultObjectSecurityPolicy, you must have the #DefaultObjectSecurityPolicy privilege. To modify the defaultObjectSecurityPolicy of another user, you must have this privilege, and write access to the security policy of that UserProfile.

 

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.

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 8.1 describes the types of functions that each privilege controls.

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.

Attempting to removing privileges from SystemUser has no effect, only SystemUser can remove privileges for other special users.

Table 8.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 other than your own. This includes adding or removing a SymbolDictionary to/from a SymbolList that is not your own. OtherPassword is also needed 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 system operations that access external files, including operations related to backup, restore, transaction logs, and extents. This does not affect application access using GsFile.

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:, execute code using GsHostProcess, nor access the GsSysLog; except for executables that are white-listed (described on here)

NoUserAction

If you have this privilege, you cannot execute System class>> loadUserActionLibrary:, and use of the FFI interface is disallowed.

NoGsFileOnServer

If you have this privilege, you cannot execute any 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. This privilege is required for all developers writing 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 or 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.

The inverse privileges, NoGsPerformOnServer and related

Some operations, like executing performOnServer:, are allowed by default for all users. Since access to operating system functionality and disk files is a security issue, this functionality can be disabled for specific users by assigning an inverse privilege.

For the privileges NoGsPerformOnServer, NoUserAction, NoGsFileOnServer, and NoGsFileOnClient, the user that has that privilege is disallowed from performing the associated operations.

Whitelist for performOnServer:

Accessing OS functions using performOnServer: (and the related GsHostProcess functionality) is very useful, and for some applications it may be necessary for users that should not have general OS command access, to be able to execute specific OS commands.

This can be set up via a per-user whitelist. To allow a user with the NoPerformOnServer privilege to execute a specific command, the full path (starting with / and ending with the command name) to the command is put on a whitelist for that specific user. The full path must then be specified on the performOnServer: or GsHostProcess method argument; the paths and commands must match exactly for the execution to be permitted.

There are no checks or restrictions on arguments to the commands; only the commands themselves are examined and allowed (or not).

The following methods add one or more commands to the whitelist:

UserProfile >> addPerformOnServerCommand: fullPathToCommand
UserProfile >> addPerformOnServerCommands: arrayOfCommands

To report the existing whitelist:

UserProfile >> performOnServerCommands

To remove commands from the whitelist:

UserProfile >> removeAllPerformOnServerCommands
UserProfile >> removePerformOnServerCommand: aString 
UserProfile >> removePerformOnServerCommands: arrayOfString

The requirement to use full paths in the whitelist and in the argument to performOnServer: applies to operating system commands such as ls. While users without NoPerformOnServer can pass commands such as 'pwd' and 'ls' to performOnServer:, and rely on the path lookup, users with NoPerformOnServer must configure the whitelist to include the full path, such as '/bin/pwd' and '/bin/ls', and pass the same full path to performOnServer:.

For example, as DataCurator or another privileged user, update a non-privileged user ReadingUser by executing:

(AllUsers userWithId: 'ReadingUser') addPrivilege: 'NoPerformOnServer'; addPerformOnServerCommand: '/usr/bin/git'.
System commitTransaction.

Now, as ReadingUser, the following will succeed:

System performOnServer: '/usr/bin/git log'

But ReadingUser cannot perform other OS commands, such as ls or cp. Each individual executable to be allowed must be specifically entered in the whitelist.

DeletedUserProfile and AllDeletedUsers

Removing a user requires some cleanup to ensure that the references to or from the UserProfile do not allow premature garbage collection of objects that are needed, or prevent garbage collection entirely. There are two kinds of references of concern:

  • references from an instance of GsObjectSecurityPolicy to the user, which can prevent the UserProfile from being garbage collected
  • references from the UserProfile to the symbol list and to objects that are private to the user that is being deleted, which may need to be retained.
  • references from UserProfileGroups

To avoid the risk of problems, removing a user requires using specific protocol that performs cleanup.

These methods update any GsObjectSecurityPolicies to be owned by either the current user or a specified user, and removes the user from each group in AllGroups. To ensure that objects whose only reference is from the deleted user are not prematurely lost, when the instance of UserProfile is deleted a new instance of DeletedUserProfile is created, and added to the globals AllDeletedUsers. This DeletedUserProfile has a reference to the SymbolList of the deleted user, as well as the userId and the date the user was deleted.

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.

8.2  UserProfileGroups

GemStone uses a combination of group memberships and GsObjectSecurityPolicy group permissions to control access to groups of objects by groups of users.

UserProfileGroups are also used to handle KerberosPrincipals that will be shared by multiple UserProfiles for #SingleSignOn authentication.

A UserProfileGroup maps a group name to a set of UserProfiles. For compatibility with past releases, protocol in UserProfile and GsObjectSecurityPolicy may accept arguments of the group name instead of, or in addition to, the UserProfileGroup instance.

AllGroups

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

Initially, AllGroups contains the following predefined groups:

Table 8.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 for future use).

By default, all new users become members of group Subscribers.

Groups for object authorization

It is common that certain objects must be protected from read or write access by other users in the system (the “world”), while still being accessible to specific individual users. By creating a group, adding authorization for that group to the GsObjectSecurityPolicy that protects these objects, and by making the user a member of the group, you can provide that user with the appropriate access to these objects.

A GsObjectSecurityPolicy can authorize multiple groups and a user can be a member of multiple groups.

Create a group

To create a new group and add it to AllGroups, use:

UserProfileGroup class >> newGroupWithName: 

To lookup an existing group by name, execute one of:

UserProfileGroup class >> groupWithName:
UserProfileGroup class >> groupWithName:ifAbsent:
UserProfileGroup class >> groupWithName:otherwise:

Delete a group

Deleting a group removes it from all User accounts that are members, as well as from AllGroups.

UserProfileGroup class >> deleteGroup:
UserProfileGroup class >> deleteGroupWithName:
UserProfileGroup class >> deleteGroupWithName:ifAbsent:

8.3  Creating and Removing Users

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

In addition to creating the new UserProfile, you should also ensure 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, creating an instance of DeletedUserProfile in the AllDeletedUsers global.

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'
	createNewSecurityPolicy: 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 one or more groups, and specify a default ObjectSecurityPolicy. The ObjectSecurityPolicy may be nil.

AllUsers addNewUserWithId: 'theUserId'
    password: 'thePassword'
    defaultObjectSecurityPolicy: anObjectSecurityPolicyOrNil
    privileges: anArrayOfPrivSyms
    inGroups: aCollectionOfGroupsOrGroupNames

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, and the user be removed from all groups. 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.

For more details on how this is handled, see DeletedUserProfile and 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).

AllUsers removeAndCleanup: aUserProfile.
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.

AllUsers removeAndCleanup: aUserProfile
	migrateSecurityPoliciesTo: anotherUserProfile.
AllUsers removeAndCleanupUserWithId: ‘aUserId’
migrateSecurityPoliciesToUserWithId: ‘anotherUserId’
ifAnyAbsent: aBlock

For example,

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

Users and Group membership

You can specify group memberships when creating users. Alternatively, you can add existing users to a UserProfileGroup, or add existing groups to a UserProfile. The methods to do this ensure that the set of groups that a UserProfile references matches the UserProfile references in the groups.

Adding a user to a group

UserProfileGroup >> addUser: aUserProfile
Add the given UserProfile to the receiver, and add the receiver to this UserProfile’s groups.

UserProfile >> addGroup: ’groupNameString’
Add the UserProfileGroup with the given name to the receiver, and add the receiver to the UserProfileGroup.

UserProfile >> addToUserProfileGroup: aUserProfileGroup
Add the given UserProfileGroup to the receiver, and add the receiver to the UserProfileGroup.

Removing a user from a group

UserProfileGroup >> removeUser: aUserProfile
remove the given UserProfile from the receiver, and remove the receiver from this UserProfile’s groups.

UserProfile >> removeGroup: ’groupNameString’
Remove the UserProfileGroup with the given name from the receiver, and remove the receiver from the UserProfileGroup.

UserProfile >> removeFromUserProfileGroup: aUserProfileGroup
Remove the given UserProfileGroup from the receiver, and remove the receiver from the UserProfileGroup.

Querying for Group members

UserProfileGroup >> users
Return a set of UserProfiles that belong to the receiver.

UserProfileGroup >> userIds
Return a set of UserIds for the UserProfiles that belong to the receiver.

Querying for a User’s Groups

UserProfile >> groups
Return the set of UserProfileGroups that this user belongs to.

UserProfile >> groupNames
Return the group names for each UserProfileGroup that this user belongs to.

for example

! Create group
UserProfileGroup newGroupWithName: 'Sales'.
System commitTransaction.
! Add users
(UserProfileGroup groupWithName: 'Sales') 
	addUser: (AllUsers userWithId: 'DataCurator');
	addUser: (AllUsers userWithId: 'GcUser').
! report the UserIds in that group
(UserProfileGroup groupWithName: 'Sales') userIds
%
anIdentitySet( 'DataCurator', 'GcUser')

8.4  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
#6 HostAgentUser

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

Accounts that use external authentication (Unix, LDAP, SingleSignOn, and X509) do not manage the password within GemStone. This section applies only to accounts using GemStone authentication.

Users Changing Their Own Password

Privileges required: UserPassword, and the account must be using GemStone authentication.

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, and the other user must be using GemStone authentication.

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 that controls access to this security policy, 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: #authSym.
theObjectSecurityPolicy worldAuthorization: #authSym.
theObjectSecurityPolicy group: groupOrGroupString authorization: #authSym.

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. See under Create a group for information on creating a new 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: 'groupOrGroupString' 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 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 Symbols. Table 8.1 lists the Smalltalk operations controlled by 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: aPrivilegeSym.

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: aPrivilegeSym.

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: anArrayOfSym 

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. This change will affect other sessions that are logged in as this user when the other session commits or aborts, unless the other session is using a transient symbol list.

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.

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

UserProfiles using GemStone authentication can be disabled in two ways; automatically, by violating password controls such as on the number of failed logins; and explicitly, by sending messages such as disable. An account that is disabled in GemStone cannot log in.

UserProfiles using external authentication can also be explicitly disabled in GemStone. However, they may also be unable to login due to violating the password controls enforced by the external authentication. GemStone does not detect, report, or re-enable accounts that are disabled by the external authentication mechanism. If the Unix or LDAP account cannot log in, or if Kerberos does not have a current ticket, logins will fail and you must correct the problems directly in Unix, LDAP, or Kerberos.

Once an account is disabled in GemStone, it must be re-enabled in GemStone before the account can log in again. For accounts using GemStone authentication, a new password must be assigned for that account by an administrator.

Explicitly Disable an Account in GemStone

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

Users using any authentication scheme can be disabled using the methods disable or disableWithReason:. This prevents the user from logging in again, but does not affect currently logged in sessions.

For example:

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

Privileges required: OtherPassword.

To re-enable an account that uses GemStone authentication, an administrative user must login and reset the users’s password. An account that becomes disabled, whether explicitly disabled or from security violations, will clear its password.

Users using GemStone authentication can be re-enabled using the methods password: or reenableWithPassword:.

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') 
	reenableWithPassword: 'AaBbCc';
	loginsAllowedBeforeExpiration: 1.
System commitTransaction
Re-enable an Account that is using external authentication

Accounts using external authentication rely on the security procedures for the external authentication scheme. If the external authentication disables logins, the user will not be able to login using GemStone, but the account in GemStone is not disabled.

Accounts that were explicitly disabled in GemStone can be re-enabled using the method reenable . Since the password is managed outside GemStone, you do not need to supply a new password.

topaz 1> printit
(AllUsers userWithId: 'Mary') reenable.
System commitTransaction
Find Out Which Accounts Have Been Disabled in GemStone

Privileges required: OtherPassword.

The message AllUsers findDisabledUsers returns a SortedCollection of UserProfiles that were either disabled explicitly, or that use GemStone authentication and were disabled by a security precaution such as password expiration, login failure, etc. Accounts using external authentication and that have been disabled by the rules for that authentication are not disabled in GemStone, and will not be returned by this method.

For example:

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

See Re-enable an Account that is using GemStone authentication for how DataCurator or another user with the OtherPassword privilege can reactivate an account.

Check If an Account Is Disabled

Privileges required: OtherPassword.

You can check if a particular account is disabled in GemStone by sending the message isDisabled to the account’s UserProfile.

For example:

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

Privileges required: OtherPassword.

You can find out why a particular account was disabled in GemStone by sending the message reasonForDisabledAccount to the account’s UserProfile.

For example:

topaz 1> printit
(AllUsers userWithId: 'Sam') 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.

This restriction is unrelated to authentication, and applies equally to all authentication schemes.

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 session/s.

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

8.5  Configuring GemStone Authentication

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 – especially SystemUser, DataCurator and GcUser.

GemStone authentication always requires a password to be set in the UserProfile. When you enable GemStone authentication, you must provide an initial password.

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

Configuring GemStone Login Security

When authentication is done using #GemStone, there are a number of login security features available. You can:

  • Constrain the choice of passwords to a certain pattern, ban particular passwords altogether, or ban reuse of a password by the same account
  • Require users to change their passwords periodically (password aging)
  • Limit the number of logins under a temporary password
  • Disable accounts that have not logged in for a specified interval (account aging)
  • Limit the number of concurrent sessions by a particular account
  • Monitor failed login attempts and, if necessary, disable further login attempts on that account

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 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 8.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 8.3).

Table 8.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 query for 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 set. For instance:

(AllUsers disallowedPasswords) 
	addAll: #( 'Mother' 'apple_pie' ) 

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.
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:

AllUsers disallowUsedPasswords: true.
AllUsers numberOfDisallowedPasswords: 10.
Clearing a User’s Disallowed Old Passwords

Privileges required: OtherPassword.

You can clear the set of old passwords so that they can be reused. 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:

(AllUsers userWithId: 'Mary') 
	clearOldPasswords.

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, for example to 120 days:

AllUsers passwordAgeLimit: 120 * 24 .

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 that is using GemStone authentication 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. For example, to set the limit for a Mary to 5 days:

(AllUsers userWithId: 'Mary') 
	passwordAgeLimit: 5 * 24.

If repository-wide password aging is enabled, you can also override this for individual users, such as managers or administrators, either by setting the password page limit to 0, or setting the password to never expire. For example, to prevent the password used by batch jobs from expiring, execute:

(AllUsers userWithId: 'BatchUser') 
	setPasswordNeverExpires: true.
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. 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 a specific user. For example, to warn Mary within three days of the time her password will expire, do this:

(AllUsers userWithId: 'Mary') passwordAgeWarning: 3 * 24.
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
%
08/24/20 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 that is using GemStone authentication 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.

(AllUsers userWithId: 'Auditor') 
	staleAccountAgeLimit: 180 * 24.
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
%
08/24/20 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.

Use caution in enabling account aging on existing repositories. Enabling account aging may result in user accounts being disabled, if there happens to be a lastLoginDate recorded in the UserProfile. This may be due to a login date recorded in an earlier period due to changes in login security in your application, or UserProfiles in an repository progressively upgraded from historical versions of GemStone that routinely set the lastLoginDate.

To avoid this issue, 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 that is using GemStone authentication 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. 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:

---Mon 24 Aug 2020 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 that is using GemStone authentication for details.

8.6  Configuring UNIX Authentication

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 restriction, aging, and failed login 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.

(AllUsers userWithId: 'Mary') 
	enableUnixAuthenticationWithAlias: 'msmith'.
 
(AllUsers userWithId: 'msmith') 
	enableUnixAuthenticationWithAlias: nil.

To verify that the UNIX userId exists, execute:

System unixUserIdExists: UNIXUserId

8.7  Configuring LDAP Authentication

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.

8.8  Configure SingleSignOn Authentication

If your system is setup with Kerberos, you can configure GemStone authentication to use SingleSignOn, which checks for a current Kerberos ticket and avoids the requirement that a user should provide a password on login. Using SingleSignOn (SSO), the “User Password” field in the login parameters is left empty.

When Kerberos is not running or has no current ticket, the user cannot login. For this reason, SystemUser may not be setup to use SSO. SystemUser always requires GemStone authentication.

Kerberos concepts

For details on how to install or configure Kerberos at your site, consult your System Administrators. GemStone uses Kerberos v5 Release 1.15 with AES encryption.

Note that on Windows, Kerberos is built in as part of SSPI, and SingleSignOn can only be used on Windows clients that are part of a Windows Domain, either Samba or an MS Windows server.

Kerberos uses the term "realm" for a domain within an installation/organization. Realms are generally the DNS domain in upper case; for example, at GemTalk the realm would be GEMTALKSYSTEMS.COM.

The term "principal" is used for any unique identity, including UNIX user ids, hosts, and services.

User principles are identified as name@REALM, e.g.

maryb@BIGCORPORATION.COM 

When a user authenticates using Kerberos (e.g by UNIX login or by using kinit), the user enters their password, and Kerberos provides a ticket—more specifically, a ticket granting ticket (TGT). This is cached on the user’s host, and is used to create specific tickets for requested services at the time they are requested. The TGT is usually put under /tmp, and is valid for a limited period, by default usually 10 hours.

In addition to Kerberos user principals, you will need to create a Kerberos service principal for GemStone. The service principal is of the form Service/fully.qualified.domain.name@REALM. The name of the service principal for authentication in GemStone is GEMSTONE64. So, for example, a service principal might be

GEMSTONE64/nodename.bigcorporation.com@BIGCORPORATION.COM

You will need to create a keytab file with the GEMSTONE64 service principals for each individual host node. This keytab file will be used for authorization; any user who has authenticated via Kerberos and has access to the keytab file, and has their GemStone user account setup for SingleSignOn, will be able to do a passwordless login to GemStone.

KerberosPrincipal and AllKerberosPrincipals

Instances of the class KerberosPrincipal define the association between the name of a Kerberos principal and GemStone login information. An instance of KerberosPrincipal has the following information:

name

(Symbol) the name for the kerberos principal; must be unique.

loginUserProfile

a UserProfile, or nil for a shared KerberosPrincipal.

loginUserProfileGroups

an IdentitySet of UserProfileGroups, to support a shared KerberosPrincipal, or nil.

loginAsAnyoneEnabled

a Boolean; true if any UserProfile can use this KerberosPrincipal.

There are a number of ways that a repository can be configured to allow one or more UserProfiles to login using one or more KerberosPrincipals.

  • There may be a one-to-one relationship between a UserProfile and a KerberosPrincipal, with each referencing the other. In this case loginUserProfileGroups and loginAsAnyoneEnabled are usually nil.
  • One or more UserProfile/s may reference a KerberosPrincipal with loginUserProfileGroups configured as a set of UserProfileGroups. The KerberosPrincipal has a nil loginUserProfile and loginAsAnyoneEnabled set to false. Any user that is a member of any of these groups can use this KerberosPrincipal to log in.
  • One or more UserProfile/s may reference a KerberosPrincipal that has a nil loginUserProfile, and with loginAsAnyoneEnabled set to true. This allows any user to configure SingleSignOn using that KerberosPrincipal.

KerberosPrincipals are created using class protocol in KerberosPrincipal, and are automatically added to the global collection AllKerberosPrincipals. You can look up a KerberosPrincipal by name. The AllKerberosPrincipals global should not be accessed directly.

Setting up Kerberos Authentication in GemStone

There are several steps to setting up SingleSignOn for a user account. You must:

  • Create the KerberosPrincipal specific to that user
  • Use that KerberosPrincipal as an argument to enable #SingleSignOn authentication
  • Configure the environment for the user that will login with the keytab file location.
Create an instance of KerberosPrincipal for the User

A UserProfile who will use SSO is associated with a specific instance of KerberosPrincipal. For example, to create a Kerberos user principle for the GemStone user named Mary, use an expression such as this:

KerberosPrincipal 
	newPrincipalWithName: 'maryb@BIGCORPORATION.COM' 
	loginUserProfile: (AllUsers userWithId: 'Mary').

This creates the KerberosPrincipal and adds it to AllKerberosPrincipals.

Enable SingleSignOn for that UserProfile using the new KerberosPrincipal

Use the Kerberos Principle when enabling SingleSignOn, using UserProfile >> enableSingleSignOnAuthenticationWithPrincipal:.

Configure the gem’s environment to set the keytab file location

The Gem configuration parameter GEM_KERBEROS_KEYTAB_FILE must be set in order to specify the location of the keytab file.

For example, enter this in the configuration file that will be used by Gem sessions. This may be the default $GEMSTONE/data/system.conf, or another configuration file, as described in How GemStone Uses Configuration Files.

GEM_KERBEROS_KEYTAB_FILE ='/export/localnew/common/kerberos/gemtalk.keytab';
Verify login without password

Verify authentication; make sure the user account for maryb is authenticated using Kerberos, enter the GemStone user name (Mary) in the login parameters, and leave the password empty.

Example 8.1 Setting up SingleSignOn for a single account

| prin user |
user := AllUsers userWithId: 'Mary'.
prin := KerberosPrincipal 
	newPrincipalWithName: 'maryb@GEMTALKSYSTEMS.COM' 
	loginUserProfile: user.
user enableSingleSignOnAuthenticationWithPrincipal: prin.
System commitTransaction.
 

Using Groups to authenticate with Kerberos

A KerberosPrincipal often will correspond to a specific GemStone UserProfile. However, you may use groups to configure your system so multiple GemStone UserProfiles can login using the same KerberosPrincipal.

To do this, create a KerberosPrincipal with a nil loginUserProfile. Create a new group and make the users part of the group, and add the group to the KerberosPrincipal. You can then use this KerberosPrincipal to enable SingleSignOn for all the users in the group

For example, the following code uses jsmith’s principal to login as both ’john’ and ’mary’.

Example 8.2 Setting up SingleSignOn using Groups

| prin user1 user2 group |
user1 := AllUsers userWithId: 'john'.
user2 := AllUsers userWithId: 'mary'.
group := UserProfileGroup 
	newGroupWithName: 'KerberosAdminLogin'.
user1 addGroup: group groupName.
user2 addGroup: group groupName.
prin := KerberosPrincipal newPrincipalWithName:
	'jsmith@GEMTALKSYSTEMS.COM' loginUserProfile: nil.
prin addGroup: group.
group users do: [:aUserProfile | aUserProfile
	enableSingleSignOnAuthenticationWithPrincipal: prin].
System commitTransaction.
 

KerberosPrincipal available to all users

You can configure all users on your system to share the KerberosPrincipal by using

aKerberosPrincipal loginAsAnyoneEnabled: true

This is equivalent to creating a group that includes all users and using this for the KerberosPrincipal’s group.

Then, any user (except SystemUser) can set their authentication to #SingleSignOn using the principal aKerberosPrincipal.

Example 8.3 Sharing a KerberosPrincipal

| prin user1 user2 |
user1 := AllUsers userWithId: 'Mary'.
user2 := AllUsers userWithId: 'John'.
prin := KerberosPrincipal 	newPrincipalWithName:
	'jsmith@GEMTALKSYSTEMS.COM' 	loginUserProfile: nil.
prin loginAsAnyoneEnabled: true.
user1 enableSingleSignOnAuthenticationWithPrincipal: prin.
user2 enableSingleSignOnAuthenticationWithPrincipal: prin.
System commitTransaction.
 

8.9  Tracking User Logins

GemStone provides to options related to user logins: logging of each user login and logout, and a hook that allows you to execute specific code when a specific user logs in.

These features apply for all authentication schemes.

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.

Specific UserProfiles can be configured to not log events on login and logout; this can help avoid having the log cluttered with system logins. For example, you may not want to track when the GcUser logs in and out. This is done with the method UserProfile >> disableLoginLogging. After this is executed, the particular 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 LoginUserId KerberosPrincipal

For example:

"04/28/20 10:49:13.404 PDT" 1488307753 STARTUP Stone 0 2045 631 631 localhost 127.0.0.1 0.0.0.0 0 631 ''

"04/28/20 10:51:04.111 PDT" 1488307864 LOGIN DataCurator 5 2641 631 631 localhost 127.0.0.1 127.0.0.1 0 631 ''

"04/28/20 10:51:05.792 PDT" 1488307865 SHUTDOWN Stone 0 2045 631 631 localhost 127.0.0.1 0.0.0.0 0 631 ''

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, then 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.
%

Debugging

The loginHook code will be executed each time that user logs in. If there are errors in the loginHook code, the login does not complete and the user will not be able to log in.

Logging in as another user with OtherPassword privilege may be required, to reset the loginHook code and allow the user to login.

SystemUser can enable to configuration parameter STN_ALLOW_NO_SESSION_INIT using the runtime parameter #StnAllowNoSessionInit. When this is set, the login reports the error but will complete, which may allow you to debug the loginHook code.

Previous chapter

Next chapter