!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: userpro.gs,v 1.28 2008-01-09 22:50:20 stever Exp $
!
! Superclass Hierarchy:
!   UserProfile, Object.
!
!=========================================================================

removeallmethods UserProfile
removeallclassmethods UserProfile

category: 'For Documentation Installation only'
classmethod: UserProfile
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'Each instance of UserProfile contains a number of characteristics associated
 with a given system user.  For more information, see the GemStone System
 Administration Guide and the GemStone Programming Guide.'.
doc documentClassWith: txt.

txt := (GsDocText new) details:
'Obsolete in GemStone 5.0. 

 An Array of Symbols that identify the character set and language used during
 compilation.  Valid compiler language names were #ASCII and #JIS-EUC.'.
doc documentClassVar: #LanguageNames with: txt.

txt := (GsDocText new) details:
'An Array of Symbols that identify the privileges (that is, the levels of
 access to certain privileged system functions) that may be assigned to a
 UserProfile.  See UserProfile | privileges for a list of privilege names and
 their associated privileged methods.'.
doc documentClassVar: #PrivilegeNames with: txt.

txt := (GsDocText new) details:
'An InvariantString derived from the password that the user supplies for 
 identification purposes at login.  Limited to 1024 Characters.

 This instance variable is obsolete after repository conversion from a GemStone
 4.1 repository.  It is not used in GemStone 5.0.'.
doc documentInstVar: #encryptedPassword with: txt.

txt := (GsDocText new) details:
'A String that identifies the user to the system at login; limited to
 1024 Characters.  Methods in this class enforce uniqueness by the value of
 all user IDs of UserProfiles in AllUsers.'.
doc documentInstVar: #userId with: txt.

txt := (GsDocText new) details:
'An Array of SymbolDictionaries that are used for resolving compile-time
 Symbols.'.
doc documentInstVar: #symbolList with: txt.

txt := (GsDocText new) details:
'The Segment in which new objects are stored after each login.'.
doc documentInstVar: #defaultSegment with: txt.

txt := (GsDocText new) details:
'A SmallInteger describing the level of access to certain privileged system
 functions that are ordinarily performed by the GemStone data curator.'.
doc documentInstVar: #privileges with: txt.

txt := (GsDocText new) details:
'An IdentitySet; the groups to which the user belongs.'.
doc documentInstVar: #groups with: txt.

txt := (GsDocText new) details:
'Reserved for future use.'.
doc documentInstVar: #spare1 with: txt.

txt := (GsDocText new) details:
'Reserved for future use.'.
doc documentInstVar: #spare2 with: txt.

txt := (GsDocText new) details:
'Reserved for future use.'.
doc documentInstVar: #spare3 with: txt.

txt := (GsDocText new) details:
'Obsolete. 
 Was a SmallInteger describing the current compiler language environment.'.
doc documentInstVar: #compilerLanguage with: txt.

txt := (GsDocText new) details:
'Methods in this category are obsolete and are provided only for compatibility
 with earlier releases of GemStone.  They will be removed in a future release.'.
doc documentCategory: #'Backward Compatibility' with: txt.

txt := (GsDocText new) details:
'Methods in this category are obsolete and are provided only for compatibility
 with earlier releases of GemStone.  They will be removed in a future release.'.
doc documentClassCategory: #'Backward Compatibility' with: txt.

self description: doc.
%

category: 'Instance Creation'
classmethod: UserProfile
new

"Disallowed.  To create a new UserProfile, use newWithUserId:... instead."

self shouldNotImplement: #new
%

category: 'Backward Compatibility'
classmethod: UserProfile
newWithUserId: userIdString
password: aString
defaultSegment: aSegment
privileges: anArrayOfStrings
inGroups: aCollectionOfGroupStrings
compilerLanguage: aLangString

"Obsolete in GemStone 5.0.  Use the
 newWithUserId:password:defaultSegment:privileges:inGroups: method instead.

 GemStone 5.0 ignores the language environment setting.  Instead, the compiler
 uses the class of the sourceString to control character-set dependent
 processing during compilation."
 
^ self newWithUserId: userIdString
    password: aString
    defaultSegment: aSegment 
    privileges: anArrayOfStrings
    inGroups: aCollectionOfGroupStrings
%

category: 'Private'
classmethod: UserProfile
_validatePrivilege

" You must have #OtherPassword to modify UserProfiles "

  System myUserProfile _validatePrivilegeName: #OtherPassword
%

category: 'Private'
method: UserProfile
_initialize

"Private.  Initialize private instance variables."

self _validatePrivilege.
compilerLanguage := 1 
%

category: 'Instance Creation'
classmethod: UserProfile
newWithUserId: userIdString
password: passwordString
privileges: anArrayOfStrings
inGroups: aCollectionOfGroupStrings

"Creates a UserProfile with defaultSegment nil (new objects are created
  with World write permission."

self _validatePrivilege.
^ self newWithUserId: userIdString
	password: passwordString
	defaultSegment: nil 
	privileges: anArrayOfStrings
	inGroups: aCollectionOfGroupStrings
%

category: 'Instance Creation'
classmethod: UserProfile
newWithUserId: userIdString
password: passwordString
defaultSegment: aSegment
privileges: anArrayOfStrings
inGroups: aCollectionOfGroupStrings

"Creates a new UserProfile with the associated characteristics.  In so doing,
 creates a symbol list with three dictionaries: UserGlobals, Globals, and
 Published.  The first Dictionary (UserGlobals) is created for the user's
 private symbols.  Returns the new UserProfile.

 Adds the new UserProfile to AllUsers.  Creates the new UserProfile as a member
 of the group 'Subscribers' and of the groups in aCollectionOfGroupStrings.

 Generates an error if passwordString is equivalent to userIdString ignoring
 case (equalsNoCase:).

 aSegment must be nil or a committed segment.
"

| result newDict newSymList myAssociations mySymbols 
  userGroups newPrivSet |

self _validatePrivilege.
userIdString _validateClasses: #[ String, DoubleByteString] .
passwordString _validateClass: String.
(passwordString equalsNoCase: userIdString) ifTrue:[ 
  ^ passwordString _error: #rtDisallowedPassword 
  ].
aSegment == nil ifFalse:[ 
  aSegment _validateClass: Segment  .
  aSegment isCommitted ifFalse:[ aSegment _error: #rtErrObjMustBeCommitted ].
].
newPrivSet := UserProfile _privilegesSetFromArray: anArrayOfStrings .

aCollectionOfGroupStrings _validateClass: Collection.
userGroups := Array new .
aCollectionOfGroupStrings do: [ :aStr | | groupStr |
  aStr _validateClass: CharacterCollection .
  groupStr := AllGroups _validateGroupString: aStr .
  userGroups add: groupStr .
  ].
userGroups add: 'Subscribers' . "by default all users are a member."

       "Create the new UserProfile and populate it using the given
        arguments."

Segment setCurrent: (Globals at: #AllUsers) segment  while:[ 
  | theUserId |
  result := super new _initialize .
  theUserId := userIdString asString copy immediateInvariant .
  result _userId: theUserId .
  result privileges: anArrayOfStrings.
  userGroups do: [ :each | result addGroup: each ].
].
result defaultSegment: aSegment .

   "Create UserGlobals Dictionary "
newDict := SymbolDictionary new.
newDict changeToSegment: aSegment .

   "Create all SymbolAssociations and Symbols and UserGlobals."

myAssociations := Array new.
mySymbols := Array new.
"it is important that the following Symbols are created by send withAll:
 instead of just using the literal, so that a new object will be
 generated each time this is run"
myAssociations add: (SymbolAssociation newWithKey: #UserGlobals
                                       value: newDict).
myAssociations add: (SymbolAssociation newWithKey: #MinutesFromGMT
                                       value: 0). 
myAssociations add: (SymbolAssociation newWithKey: #NativeLanguage
                              value: #English) .
myAssociations do:[:assoc| 
  assoc changeToSegment: aSegment .
  newDict add: assoc 
].

"Create the new user's symbol list."

newSymList := SymbolList new .
newSymList add: newDict; add: Globals .
newSymList add: 
  ((AllUsers userWithId: 'DataCurator') symbolList objectNamed: #Published) .
result symbolList: newSymList.
newSymList changeToSegment: aSegment.

Segment setCurrent: (Globals at: #AllUsers) segment  while:[
  AllUsers add: result .  "add to AllUsers after all of above succeeded"
    " password must be assigned after adding to AllUsers"
  result password: passwordString.
].
^ result
%

category: 'Browser Methods'
method: UserProfile
_removeSymbol: aSymbol

"Remove aSymbol from the Symbol List by search the symbol list for an
 Association whose key is equal to aSymbol.  Returns true if successful.
 Otherwise no such Association was found, and it returns false."

| temp dict |
self _validatePrivilege.
1 to: symbolList size do:[:j |
  dict := symbolList at: j .
  temp := dict at: aSymbol ifAbsent: [nil].
  temp ~~ nil ifTrue: [
    dict removeKey: aSymbol.
    ^ true
  ].
].
^ false
%

category: 'Browser Methods'
method: UserProfile
_createDictionary: aSymbol at: index

"This method creates a new SymbolDictionary named 'aSymbol' and inserts it
 in the user's symbol list at the given 'index'."

| newDict |

self _validatePrivilege.
aSymbol _validateClass: Symbol.
index _validateClass: Integer.
newDict := SymbolDictionary new.
newDict at: aSymbol put: newDict.
self insertDictionary: newDict at: index.
^newDict
%

! fix bug 8697
category: 'Browser Methods'
method: UserProfile
_renameDictionary: aDictionary to: newName

"Renames a SymbolDictionary in the user's symbol list from its current name
 to newName."

| oldName |

self _validatePrivilege.
aDictionary _validateClass: SymbolDictionary.
newName _validateClass: Symbol.

(self resolveSymbol: newName) ~~ nil ifTrue: [
   "There's already an object named newName - don't allow the rename"
   ^false ].

"Remove the old name (if there was one)"
oldName := aDictionary name.
oldName == nil ifFalse: [
    aDictionary removeKey: oldName ].
"Create the new name"
aDictionary at: newName put: aDictionary.
^true
%

!  fix 7603

category: 'Browser Methods'
method: UserProfile
_symbolListNamesAndDictionaries

"Returns an Array of Associations which name the dictionaries in the
 receiver's SymbolList."

| result |

result := Array new .
symbolList do: [ :aDict |
  result add: 
    ( aDict associationsDetect: [ :anAssoc | anAssoc value == aDict ]
             ifNone: [ SymbolAssociation newWithKey: #unnamed value: aDict ] ).
  ].
^ result. 
%

category: 'Accessing'
method: UserProfile
defaultSegment

"Returns the default login Segment associated with the receiver."

^ defaultSegment
%

! fix bug 9732
category: 'Accessing'
method: UserProfile
groups

"Returns an IdentitySet, the set of groups to which the user belongs."

groups == nil ifTrue:[ ^ IdentitySet new ].
^ groups copy

%


category: 'Accessing'
method: UserProfile
nativeLanguage

"Returns a Symbol specifying the user's native language.  This value
 is used by error routines and other human interface routines to
 provide a system that converses in the user's native language."

^ (self resolveSymbol: #NativeLanguage) value
%

category: 'Accessing'
method: UserProfile
userId

"Each user is associated with a unique user identifier (a Symbol).
 This message returns that Symbol associated with the receiver."

^ userId
%

category: 'Updating'
method: UserProfile
addGroup: aGroupString

"Adds the user to the group represented by aGroupString, and returns the
 receiver.  If the user already belongs to the group aGroupString, no action
 occurs.

 If aGroupString does not already exist in the global object AllGroups,
 generates an error."

| theGroup userGroups |

self _validatePrivilege.
userGroups := groups .
userGroups == nil ifTrue:[
  userGroups := IdentitySet new . 
  userGroups changeToSegment: self segment .  "part of fix for 11447"
  ].
(userGroups includesIdentical: aGroupString) ifFalse:[
  theGroup := AllGroups _validateGroupString: aGroupString .
  groups == nil ifTrue:[ groups := userGroups ].
  groups add: theGroup .
  ] .
%

category: 'Updating'
method: UserProfile
defaultSegment: aSegment

"Redefines the default login Segment associated with the receiver, and returns
 the receiver.  The argument may be nil (which implies World write for 
 all newly created objects).

 If the receiver is the UserProfile under which this session is logged in,
 this method requires the DefaultSegment privilege.

 If the receiver is not the UserProfile under which this session is logged
 in, you must have write authorization for the Segment where the receiver
 resides.  Exercise extreme caution when executing this method.  If, at the
 time you commit your transaction, the receiver no longer had write
 authorization for aSegment, that user's GemStone session will be terminated
 and the user will be unable to log back in to GemStone."

aSegment == nil ifFalse:[ aSegment _validateClass: Segment ] .
(self == System myUserProfile) ifTrue: [
  ^ self _changeDefaultSegmentTo: aSegment
].
defaultSegment := aSegment .
%

! comments and arg check to fix 34642
category: 'Updating'
method: UserProfile
nativeLanguage: aLanguageSymbol

"Redefines the user's native language to be aLanguageSymbol.
 aLanguageSymbol must be a Symbol or DoubleByteSymbol .

 If you use this method, you must commit, and the change will
 take effect in sessions which login using the receiver after the commit."

self _validatePrivilege.
aLanguageSymbol _validateClass: Symbol .
((self resolveSymbol: #UserGlobals) value)
   at: #NativeLanguage
   put: aLanguageSymbol
%

category: 'Updating'
method: UserProfile
oldPassword: firstString
newPassword: secondString

"Modifies the receiver's password to be secondString.  Generates an error if
 either argument is not a String, if firstString is not the receiver's
 password, or if the receiver is not the UserProfile of the current session.
 Generates an error if secondString is equivalent to the userId of the
 receiver.  The new password (secondString) may not be longer than 
 1024 Characters.

 This method allows you to change your own password.  To change the password of
 any other user, use password: instead.

 This method requires the UserPassword privilege."

<primitive: 901> "enter protected mode"

"check old password"
(self validatePassword: firstString) ifFalse:[ 
  System _disableProtectedMode  "exit protected mode" .
  ^ userId _error: #rtErrBadPassword.
  ].
self == System myUserProfile ifFalse:[
  System _disableProtectedMode  "exit protected mode" .
  ^ self _error: #rtErrCantChangePassword
  ].
self _validateNewPassword:  secondString .
self _changePasswordFrom: firstString to: secondString .
System _disableProtectedMode  "exit protected mode" .
%

category: 'Private'
method: UserProfile
_oldPasswordsIncludes: aString

"Returns true if the receiver's old passwords include the argument."

<protected primitive: 459>

self _primitiveFailed: #_oldPasswordsIncludes: .
self _uncontinuableError
%

category: 'Private'
method: UserProfile
_validateNewPassword: aString

"Generates an error if aString is not a valid new password for the receiver.
 Otherwise, returns the receiver.  Note that the userId of the receiver is
 always a disallowed password."

<protected>

(aString equalsNoCase: userId) ifTrue:[ 
  ^ aString _error: #rtDisallowedPassword 
  ].

AllUsers _validateNewPassword: aString .

AllUsers disallowUsedPasswords ifTrue:[
  (self _oldPasswordsIncludes: aString) ifTrue:[ 
    System _disableProtectedMode  "exit protected mode" .
    ^ aString _error: #rtDisallowedPassword 
    ].
  ].
%

category: 'Accessing'
method: UserProfile
_password41

"Returns the GemStone 4.1 encrypted password, which is non-nil only if the
 receiver originated in a 4.1 repository and has never had a new password
 assigned since repository conversion to GemStone 5.0."

^ encryptedPassword
%

category: 'Updating'
method: UserProfile
password: aString

"Modifies the receiver's password to be aString, and returns the receiver.  If
 the argument is not a String, generates an error.  
 Generates an error if aString is equivalent to the userId of the
 receiver.  aString may not be longer than 1024 Characters.

 This method allows you to change the password of another GemStone user.
 To change your own password, use oldPassword:newPassword: instead.

 This method requires the OtherPassword privilege."

<primitive: 901> "enter protected mode"

self _validatePrivilege.

"no checks for format of new password, omit _validateNewPassword: "

"disallow the receivers' userId as a password"
(aString equalsNoCase: userId) ifTrue:[ 
  ^ aString _error: #rtDisallowedPassword 
  ].

self _password: aString . "change the password"

AllUsers addMsgToSecurityLog: 
'      new password set for ' , self userId , ' .' . 

System _disableProtectedMode  "exit protected mode"
%

category: 'Private'
method: UserProfile
_password: aString

"Modifies the receiver's password to be aString, and returns the receiver.  If
 the argument is not a String, generates an error.  The new password (aString)
 may not be longer than 1024 Characters.

 This method allows you to change the password of another GemStone user.
 To change your own password, use oldPassword:newPassword: instead.

 This method requires the OtherPassword privilege."

<protected primitive: 309>
aString _validateClasses: #[ String, DoubleByteString ].
aString class isBytes ifFalse:[ aString _error: #objErrNotByteKind ] .
self _primitiveFailed: #_password: .
self _uncontinuableError
%

! fix 12574 
category: 'Updating'
method: UserProfile
removeGroup: aGroupString

"Removes the user from the group represented by aGroupString.  If aGroupString
 is not a group defined in the global collection AllGroups, generates an error.
 If the user does not belong to the group, no action occurs."

| theGroup |

self _validatePrivilege.
theGroup := AllGroups _validateGroupString: aGroupString .
groups size > 0 ifTrue:[
  groups remove: theGroup ifAbsent:[ ^ self ].
  ]
%

! fixed bug 14712
category: 'Updating'
method: UserProfile
userId: newUserIdString

"Modifies the userId associated with the receiver to be newUserIdString.
 newUserIdString must not be the user ID of some other UserProfile in 
 AllUsers, or an error will be raised.  Has no effect if newUserIdString
 is equal to the current userId of the receiver.

 newUserIdString must be a kind of String , with size <= 1024 characters .

 Requires write authorization to the Segment DataCuratorSegment, 
 and requires OtherPassword privilege."
 
<primitive: 901>
| oldUserPro newUserId oldUserId duplicateUser|

System myUserProfile _validatePrivilegeName: #OtherPassword .
newUserIdString _validateClass: String .
newUserIdString size > 1024 ifTrue:[
  newUserIdString _error: #errArgTooLarge args:#[ 1024 ].
  ].
oldUserId := userId .
newUserId := newUserIdString asString .
oldUserId = newUserId ifTrue:[ 
  System _disableProtectedMode.
  ^ self 
  ].
oldUserPro := AllUsers userWithId: oldUserId ifAbsent:[ nil ].
   
duplicateUser := AllUsers userWithId: newUserId ifAbsent:[ nil ].
duplicateUser ~~ nil ifTrue:[ 
  System _disableProtectedMode  "exit protected mode".
  ^ self _error: #rtErrUserIdAlreadyExists args: #[ newUserIdString ]
  ].

oldUserPro ~~ nil ifTrue:[
  AllUsers _oldUserId: oldUserId newUserId: newUserId for: self
  ].

userId := newUserId .
System _disableProtectedMode  "exit protected mode".
%

category: 'Updating'
method: UserProfile
_userId: newUserId

"Modifies the userId associated with the receiver to be aString."

self _validatePrivilege.
userId := newUserId .
%

category: 'Updating'
method: UserProfile
_changeDefaultSegmentTo: aSegment

"Change the default login Segment of the receiver, which is the UserProfile of
 the current session.

 This method requires the #DefaultSegment privilege, 
 and requires write authorization for the receiver.

 This method does not change the Segment into which newly created objects
 are placed, that will happen after the current session commits
 at the next login using this UserProfile .
 See also System (C) >> currentSegment:  .
  
 Gs64 v2.2 , the requirement for write authorization for the receiver is new;
 it did not exist in Gemstone/S 6.x "

<primitive: 307>
aSegment _validateClass: Segment.
self _primitiveFailed: #_changeDefaultSegmentTo: .
self _uncontinuableError
%

category: 'Private'
method: UserProfile
_changePasswordFrom: firstString to: secondString

"Private.  Change the given password of the receiver, which is the UserProfile
 of the current session (firstString), to the new given password
 (secondString)."

"Tests AllUsers.disallowUsedPasswords, and if ok, saves firstString
  in encrypted form in receivers' UserSecurityData.oldPasswords. "

<protected primitive: 308>
firstString _validateByteClass: CharacterCollection.
secondString _validateByteClass: CharacterCollection.
self _primitiveFailed: #_changePasswordFrom:to: .
self _uncontinuableError
%

category: 'Backward Compatibility'
method: UserProfile
dictionaryNames

"Obsolete in GemStone 5.0."

"Returns a formatted String describing the position and name of each Dictionary
 in the receiver's symbol list.

 This method assumes that each Dictionary in the symbol list contains an
 Association whose value is that Dictionary.  If any Dictionary does not
 contain such an Association, it is represented in the result String as
 '(unnamed Dictionary)'."

^ symbolList namesReport
%

category: 'Clustering'
method: UserProfile
clusterDepthFirst

"This method clusters the receiver's user ID, password, and symbol list.
 Because the password and user ID are assumed to be byte objects, and because
 the symbol list may contain shared elements, no further traversal of the
 objects is done.  Returns true if the receiver has already been clustered
 during the current transaction; returns false otherwise."

self cluster
  ifTrue: [ ^ true ]
  ifFalse: [
    encryptedPassword cluster.
    userId cluster.
    symbolList cluster.
    ^ false
  ]
%

category: 'Updating Privileges'
method: UserProfile
_privileges: aSmallInteger

"Modifies the user's privileges (level of access to privileged system
 functions) to the level represented by the argument aSmallInteger."

self _validatePrivilege.
aSmallInteger _validateInstanceOf: SmallInteger .
privileges := aSmallInteger
%

category: 'Updating Privileges'
method: UserProfile
addPrivilege: aPrivilegeString

"Adds aPrivilegeString to the receiver's privileges.  Generates an error if
 aPrivilegeString is not a valid privilege name (as defined in the class
 variable PrivilegeNames)."

"Example: 'System myUserProfile addPrivilege: #GarbageCollection' "

<primitive: 901> "enter protected mode"
self _validatePrivilege.
self _addPrivilege: aPrivilegeString.
System _disableProtectedMode. "exit protected mode"
%

category: 'Updating Privileges'
method: UserProfile
_addPrivilege: aPrivilegeString

< protected >

| index privSet privSymbol |

privSet := UserProfile _privilegesSetFromArray: #[ aPrivilegeString ].
privSymbol := privSet _at: 1 .
index := PrivilegeNames indexOf: privSymbol .

"Test for errInvalidPrivileges "
(index == 0) ifTrue:[ aPrivilegeString _error: #rtErrBadPriv .  ^ self ]
            ifFalse:[ privileges := privileges bitOr:( 1 bitShift: index - 1) ].
%

category: 'Updating Privileges'
method: UserProfile
deletePrivilege: aPrivilegeString

"Removes aPrivilegeString from the receiver's privileges.  Generates an error
 if aPrivilegeString is not a valid privilege name (as defined in the class
 variable PrivilegeNames)."

"Example: 'System myUserProfile deletePrivilege: #GarbageCollection'"

<primitive: 901> "enter protected mode"
self _validatePrivilege.
self _deletePrivilege: aPrivilegeString.
System _disableProtectedMode. "exit protected mode"
%


category: 'Updating Privileges'
method: UserProfile
_deletePrivilege: aPrivilegeString

< protected >

| index privSet privSymbol |

privSet := UserProfile _privilegesSetFromArray: #[ aPrivilegeString ].
privSymbol := privSet _at: 1 .
index := PrivilegeNames indexOf: privSymbol .

"Test for errInvalidPrivileges "
(index == 0) ifTrue:[ aPrivilegeString _error: #rtErrBadPriv .  ^ self ]
            ifFalse:[ privileges :=
                        privileges bitAnd:( 1 bitShift: index - 1) bitInvert
                    ]
%

category: 'Updating Privileges'
method: UserProfile
privileges: anArrayOfStrings

"Redefines the receiver's privileges to be those specified by anArrayOfStrings.
 Any privileges not contained in anArrayOfStrings will be turned off.
 Generates an error if any element of anArrayOfStrings is not a valid privilege
 name (as defined in the class variable PrivilegeNames)."

"Example: System myUserProfile privileges: #('SessionAccess' 'DefaultSegment')"


| privSet myUserProfile |
self _validatePrivilege.
myUserProfile := ( self == System myUserProfile ).
privSet := UserProfile _privilegesSetFromArray: anArrayOfStrings .

" clear all privileges -- unless this is me, 
  in which case leave #OtherPassword on "

myUserProfile 
    ifTrue: [ privileges :=  
              ( 1 bitShift: ( PrivilegeNames indexOf: #OtherPassword ) - 1 ) ]
    ifFalse: [ privileges := 0 ].

privSet do: [ :aPrivSymbol |
   self addPrivilege: aPrivSymbol .
   ].

" if me, clean up the #OtherPassword flag "
myUserProfile ifTrue: [
    ( privSet includes: #OtherPassword ) ifFalse: [
        self deletePrivilege: #OtherPassword ] ].
%

category: 'Private'
classmethod: UserProfile
_privilegesSetFromArray: anArrayOfStrings

"Convert an Array of Strings to an IdentitySet of legal Privilege names.
 Raises an error if anArrayOfStrings is not an Array, or if any String
 within anArrayOfStrings cannot be translated to a canonical Symbol
 of a legal privilege name, otherwise returns an IdentitySet of Symbols."

| result |
anArrayOfStrings _validateClass: Array.
result := IdentitySet new .
anArrayOfStrings do:[ :aString | | aSymbol |
  aSymbol :=  Symbol _existingWithAll: aString .
  aSymbol == nil ifTrue:[ aString _error: #rtErrBadPriv ].
  (PrivilegeNames includesIdentical: aSymbol) ifFalse:[
    aString _error: #rtErrBadPriv .  
    ^ self 
    ].
  result add: aSymbol 
  ].
^ result
%

category: 'Updating'
method: UserProfile
createDictionary: aSymbol

"Creates a new SymbolDictionary. The new Dictionary is created with a single
 SymbolAssociation, whose key is aSymbol and whose value is the new 
 SymbolDictionary itself.

 Also creates a SymbolAssociation in the receiver's UserGlobals dictionary, with
 aSymbol as the key and the new dictionary as its value.

 Returns the new SymbolDictionary. Generates an error if aSymbol is already
 defined in the receiver's symbol list, or if aSymbol is not a Symbol."

| newDictionary anAssoc userGlobalsOfMe |

self _validatePrivilege.
aSymbol _validateClass: Symbol.  " be sure it is a Symbol"

anAssoc := self resolveSymbol: aSymbol.
 (anAssoc == nil)
    ifFalse:[ ^ self _error: #rtErrSymAlreadyDefined args: #[aSymbol]].

newDictionary := (SymbolDictionary new).  "Create new dictionary"
"Name it by making a reference to itself"
newDictionary at: aSymbol put: newDictionary.

userGlobalsOfMe := (self resolveSymbol: #UserGlobals) value.
"Get my UserGlobals"
userGlobalsOfMe at: aSymbol put: newDictionary.

^newDictionary
%

category: 'Updating the Symbol List'
method: UserProfile
symbolList: aSymbolList

"Modifies the list of SymbolDictionaries associated with the receiver.
 If the receiver is identical to 'GsSession currentSession userProfile',
 replaces the contents of the session transient symbol
 list with the contents of aSymbolList.  If an error occurs as a result
 of the modification to the persistent symbol list, the transient list
 is left unmodified and the error is handled immediately.  If an error
 occurs as a result of the modification of the transient symbol list,
 the persistent symbol list is left in its new state, the transient
 symbol list is left in its old state, and the error is silently ignored.

 Note that if the transient and persistent lists have different contents when
 an abort transaction occurs they will not be automatically synchronized
 after the abort.  The persistent list will be rolled back to the
 committed state, but the transient list will not be rolled back."

self _validatePrivilege.
aSymbolList _validateClass: SymbolList .
(symbolList == nil) 
  ifTrue: [ symbolList := aSymbolList ]
 ifFalse: [
   "must preserve old identity to avoid authorization errors"
   symbolList size: 0.
   symbolList addAll: aSymbolList .
   ] .
(self == GsSession currentSession userProfile) ifTrue: [
        Exception category: nil number: nil do: [ :e :c :n :a | ^nil ].
        GsSession currentSession symbolList replaceElementsFrom: symbolList ].
^ self
%

category: 'Updating the Symbol List'
method: UserProfile
insertDictionary: aSymbolDictionary at: anIndex

"Inserts aSymbolDictionary into the receiver's symbol list.  If the
 receiver is identical to 'GsSession currentSession userProfile', inserts
 aSymbolDictionary into the transient session symbol list as well.  The
 insertion into the receiver's symbol list occurs first.

 If anIndex is less than or equal to the size of the receiver's symbol
 list, inserts aSymbolDictionary into the symbol list at anIndex.

 If anIndex is 1 greater than the size of the receiver's symbol list,
 appends aSymbolDictionary to the receiver's symbol list.

 If anIndex is  more than 1 greater than the size of the receiver's
 symbol list, or if anIndex is less than 1, generates an error.

 If an error occurs as the result of the persistent insertion, no
 further action is taken and an error is generated.  If the insertion
 completes correctly, aSymbolDictionary is inserted into the transient
 session symbol list.

 If anIndex is 1, prepend aSymbolDictionary to the beginning of the
 transient symbol list.

 Otherwise, finds the dictionary at (anIndex - 1) in the persistent symbol
 list, and searches for it by identity in the transient symbol list.  If
 found, insert aSymbolDictionary just after it in the transient symbol
 list.  If not found, append aSymbolDictionary to the end of the transient
 symbol list.

 If an error occurs as a result of the insertion in the transient symbol
 list, the persistent symbol list is left in its new state, the
 transient symbol list is left in its old state and the error is
 silently ignored.

 Note if the transient and persistent lists have different contents when
 an abort transaction occurs they will not be automatically synchronized
 after the abort.  The persistent list will be rolled back to the
 committed state, but the transient list will not be rolled back."

"Example: System myUserProfile
                        insertDictionary: (SymbolDictionary new) at: 3'"
| transient dict index |

self _validatePrivilege.
aSymbolDictionary _validateClass: SymbolDictionary.
self symbolList insertObject:  aSymbolDictionary at: anIndex.
(self == GsSession currentSession userProfile) ifTrue: [
  transient := GsSession currentSession symbolList.
  anIndex == 1 ifTrue: [
    transient insertObject: aSymbolDictionary at: 1.
    ]
  ifFalse: [
    dict := self symbolList at: (anIndex - 1).
    index := transient indexOfIdentical: dict.
    index > 0 ifTrue: [
      transient insertObject: aSymbolDictionary at: index + 1.
      ]
    ifFalse: [
      transient add: aSymbolDictionary.
      ].
    ].
  ].

^self.
%

category: 'Updating the Symbol List'
method: UserProfile
removeDictionaryAt: anIndex

"Removes the SymbolDictionary at position anIndex from the receiver's symbol
 list, thus decreasing the size of the receiver's symbol list by one,
 and, if the receiver is identical to 'GsSession currentSession
 userProfile', removes the SymbolDictionary at position anIndex from the
 transient symbol list's symbol list (subject to the constraints below).
 Returns the receiver's dictionaryNames String.

 Generates an error if anIndex is not both a positive integer and less than the
 size of the receiver's symbol list.  If an error occurs in removing
 from the receiver's symbol list, the transient symbol list is left
 alone, and the error is handled immediately.

 If the removal from the receiver's symbol list succeeds, search in the
 transient symbol list for a SymbolDictionary identical to the one
 removed from the transient symbol list, and if found, remove it from
 the list.  Removes only the first such element.  If no such dictionary
 is found, silently return.

 Note if the transient and persistent lists have different contents when
 an abort transaction occurs they will not be automatically synchronized
 after the abort.  The persistent list will be rolled back to the
 committed state, but the transient list will not be rolled back."

"Example: System myUserProfile insertDictionary: (SymbolDictionary new) at: 3.
                  System myUserProfile dictionaryNames.
                  System myUserProfile removeDictionaryAt: 3.
                  System myUserProfile dictionaryNames."

| dict |
self _validatePrivilege.
anIndex _validateClass: Integer.
dict := self symbolList removeAtIndex: anIndex.
(self == GsSession currentSession userProfile) ifTrue: [
        GsSession currentSession symbolList removeIdentical: dict ifAbsent: [ ]
].
^self dictionaryNames.
%

category: 'Backward Compatibility'
method: UserProfile
compilerLanguage: aString

"Obsolete in GemStone 5.0.  Has no effect.

 GemStone 5.0 ignores the language environment setting.  Instead, the compiler
 uses the class of the sourceString to control character-set dependent
 processing during compilation."

^ self
%

category: 'User Authorization'
method: UserProfile
validatePassword: aString

"This method allows an application to validate an operation that requires
 authentication by multiple individuals.  Returns true if aString is the
 password of the receiver, returns false otherwise.  Generates an error if the
 argument is not a String."

<primitive: 306>
aString _validateByteClass: CharacterCollection.
self _primitiveFailed: #validatePassword: .
self _uncontinuableError
%

category: 'Accessing the Symbol List'
method: UserProfile
dictionaryAndSymbolOf: anObject

"Returns the symbol list Dictionary that names anObject, and also returns the
 name which that Dictionary associates with anObject.  More precisely, this
 returns an Array containing two elements:

 * The Dictionary in the user's symbol list that contains an Association whose
   value is anObject.
 * The Symbol which is that Association's key.

 The symbol list is searched in the same order that the compiler searches it.
 (For more information about symbol resolution, see the GemStone Programming
 Guide.)  If anObject is not found in the symbol list, returns nil."

^ symbolList dictionaryAndSymbolOf: anObject
%

category: 'Accessing the Symbol List'
method: UserProfile
resolveSymbol: aString

"Searches the receiver's symbol list for an Association whose key is equal to
 aString, and returns that Association.  If no such Association is found in the
 symbol list, this returns nil."

^ symbolList resolveSymbol: aString
%

category: 'Accessing the Symbol List'
method: UserProfile
symbolList

"Whenever the compiler is invoked, tokens in the method's source are bound to
 either Symbols found in the source, or to SymbolAssociations found in one of a
 number of SymbolDictionary passed to the compiler.  This method returns
 the Array of such SymbolDictionary that are associated with the
 receiver."

^ symbolList
%

category: 'Accessing the Symbol List'
method: UserProfile
symbolResolutionOf: aString

"Searches the receiver's symbol list for aString.  If aString is found, returns
 a formatted String describing the position in the symbol list of the
 Dictionary defining aString, the name of that Dictionary, and aString.

 Generates an error if aString is not defined for the receiver."

"Example: System myUserProfile symbolResolutionOf: #Integer"

^ self symbolList symbolResolutionOf: aString
%

category: 'Accessing Privileges'
method: UserProfile
_privileges

"Returns a SmallInteger specifying the user's level of access to certain
 privileged system functions ordinarily performed by the GemStone data
 curator."

"SystemControl (bit 0); ObsoleteStatistics (1); SessionAccess (2);  UserPassword (3);
 DefaultSegment (4); ReservedForFutureUse (5);  OtherPassword (6); 
 SegmentCreation (7); SegmentProtection (8); FileControl (9);
 GarbageCollection (10); CodeModification (11) ;
 NoPerformOnServer (12); NoUserAction (13); NoGsFileOnServer (14);
 NoGsFileOnClient (15) "

^ privileges
%

category: 'Accessing Privileges'
method: UserProfile
_validateCodeModificationPrivilege

"Generates an error if the receiver does not have the CodeModification
 privilege.  This uses hardcoded bit number to handle filein and upgrade
 cases."

System _protectedMode ifFalse: [
  (privileges bitAt: 11) == 1 ifFalse:[ 
    self _error: #rtErrNoPriv .
    self _uncontinuableError
    ] ]
%

category: 'Accessing Privileges'
method: UserProfile
_validatePrivilege: anInt

"Generates an error if the receiver does not have the privilege specified
 by anInt."

(privileges bitAt: anInt) == 1 ifFalse:[ 
  self _error: #rtErrNoPriv .
  self _uncontinuableError
  ]
%

category: 'Accessing Privileges'
method: UserProfile
_validatePrivilegeName: aSymbol

"Generates an error if the receiver does not have the privilege specified
 by aSymbol, or if aSymbol is not the name of a privilege."

 | bitNumber |
 PrivilegeNames == nil ifTrue: [ ^self ].         "filein bootstrap case"
 bitNumber := (PrivilegeNames _indexOfIdentical: aSymbol) - 1.
 bitNumber < 0 ifTrue:[
   self _error: #rtErrNoPriv .
   self _uncontinuableError.
   ].
 self _validatePrivilege: bitNumber
%


category: 'Accessing Privileges'
method: UserProfile
privileges

"Returns an Array of Strings describing the receiver's privileges.  Those
 privilege Strings specify the user's level of access to certain privileged
 system functions ordinarily performed by the GemStone data curator.

 The following table shows the privileged methods that are associated with each
 privilege String.  If a method is listed with more than one privilege, all such
 privileges are required to execute the method.  For example, to send the
 message System | stopUserSessions, you must have both SystemControl and
 SessionAccess privileges.

 For more information about privileges, see the GemStone Programming
 Guide.

 Privileges         Privileged Methods
 ----------         ------------------
 DefaultSegment     UserProfile    | defaultSegment:

 FileControl        Repository     | abortRestore,
                                     addTransactionLog:replicate:size:,
                                     commitRestore,
                                     continueFullBackupTo:MBytes:,
                                     createExtent:,
                                     createExtent:withMaxSize:,
                                     fullBackupTo:,
                                     fullBackupTo:MBytes:,
                                     restoreFromArchiveLogs,
                                     restoreFromBackup:,
                                     restoreFromBackups:,
                                     restoreFromCurrentLogs,
                                     restoreToEndOfLog:,
                                     restoreStatus,
                                     startNewLog,
                                     shrinkExtents,
                                     setArchiveLogDirectories:...
                                        replicatePrefix:,
                                     timeToRestoreTo:

 GarbageCollection  Repository     | auditWithLimit:,
                                     findDisconnectedObjects,
                                     markForCollection,
                                     markGcCandidates,
                                     pagesWithPercentFree:,
                                     repairWithLimit:,
                                     reclaimAll,
                                     scavengePagesWithPercentFree:

 OtherPassword      UserProfile    | activeUserIdLimit,
                                     activeUserIdLimit:,
                                     isDisabled,
                                     clearOldPasswords,
                                     password:,
                                     lastLoginTime,
                                     lastPasswordChange,
                                     loginsAllowedBeforeExpiration,
                                     loginsAllowedBeforeExpiration:,
                                     reasonForDisabledAccount,
                                     reasonForDisabledAccount:,
                                     userId:

                    UserProfileSet | findDisabledUsers,
                                     findProfilesWithAgingPassword

 SegmentCreation    Segment        | newInRepository:

 SegmentProtection  Segment        | group:authorization:,
                                     groupNo:group:authorization:,
                                     ownerAuthorization:,
                                     worldAuthorization:

 SessionAccess      GsSession      | sessionWithSerialNumber:,
                                     serialOfSession:,
                                     sessionIdOfSerial:

                    System         | concurrencyMode:,
                                     currentSessionNames,
                                     descriptionOfSession:,
                                     stopUserSessions,
                                     userProfileForSession:
   
 SystemControl      GsSession      | stop

                    Repository     | reclaimAll
				     auditWithLimit

                    System         | resumeLogins,
                                     shutDown,
                                     stopUserSessions,
                                     stopSession:,
                                     suspendLogins,
                                     terminateSession:timeout:
                                     changeCacheSlotIoLimit:to:

 UserPassword       UserProfile    | oldPassword:newPassword:

 CodeModification   Behavior       | All Update methods
                    GsMethod       | All Update methods
                    GsMethodDictionary
                                   | All Update methods
                    SymbolDictionary
                                   | All Update methods
                                   | (when entry is a kind of Behavior)

 (various)          System         | configurationAt:put:

 The following 4 privileges are represented by inverse bits.
 The operation is allowed unless the bit is set.  

 NoPerformOnServer  System         | performOnServer:

	Following 3 are cached at login, see note below.

 NoUserAction       loading of any user action library.

 NoGsFileOnServer   any GsFile operation which accesses a file on the server,
		    i.e. access from the gem process to the file system.

 NoGsFileOnClient   any GsFile operation which accesses a file on the client,
		    i.e. access from the remote GCI process to the file system.

 The state of  NoUserAction, NoGsFileOnServer, NoGsFileOnClient
 are cached in the Virtual Machine at session login,
 and changes to a UserProfile for these 3 privileges will only be seen
 in sessions that login after those changes have been committed.
 "


"The following table shows the private privileged methods that are associated
 with each privilege String. 

 Privileges         Privileged Methods
 ----------         ------------------
 DefaultSegment     UserProfile    | _changeDefaultSegmentTo:

 OtherPassword      UserProfile    | _password:,
                                     _securityDataInstVarAt:,
                                     _securityDataInstVarAt:put:

 SessionAccess      System         | _descriptionOfSessionSerialNum:"


| result |

result := Array new.

1 to: (PrivilegeNames size) do: [:counter |
                          ((privileges bitAt: (counter - 1))= 1)
                          ifTrue: [
                                  result add: (PrivilegeNames at: counter).
                                  ].
                               ].
^result
%

category: 'Backward Compatibility'
method: UserProfile
compilerLanguage

"Obsolete in GemStone 5.0.

 GemStone 5.0 ignores the language environment setting.  Instead, the compiler
 uses the class of the sourceString to control character-set dependent
 processing during compilation."

^ LanguageNames at: compilerLanguage
%

category: 'Accessing the Symbol List'
method: UserProfile
objectNamed: aSymbol

"Returns the first object in the receiver's symbol list that has the given
 name.  If no object is found with the given name, this returns nil."

| assn |
assn := symbolList resolveSymbol: aSymbol.
assn == nil ifTrue: [ ^nil ].
^assn value
%

category: 'Storing and Loading'
method: UserProfile
writeTo: aPassiveObject

"Instances of UserProfile cannot be converted to passive form.  This method
 writes nil to aPassiveObject and stops GemStone Smalltalk execution with a
 notifier."

  aPassiveObject writeObject: nil.
  self _error: #rtErrAttemptToPassivateInvalidObject.
%

category: 'Repository Conversion'
method: UserProfile
_migrateGroups

"Converts the groups collection from a SymbolSet to an IdentitySet of Strings."

groups := AllGroups _migrateSymbolSet: groups .
%

category: 'Private'
method: UserProfile
_securityDataInstVarAt: offset

"Returns the value of the specified instance variable in the UserSecurityData 
 of the receiver.  If the value is not a special object, returns a copy of the
 value of that instance variable.  

 Generates an error if you do not have OtherPassword privilege."

 "offset 0 implements 'isDisabled' and returns true or false.
 The primitive fails if you attempt to access the password or the 
 oldPasswords instVars."

<protected primitive: 460>
self _primitiveFailed: #validatePassword: .
self _uncontinuableError
%

category: 'Private'
method: UserProfile
_securityDataInstVarAt: offset put: aValue

"Private.  Modifies the specified instance variable in the UserSecurityData of
 the receiver.  The sender is responsible for making a copy of aValue and
 making the copy invariant, if appropriate.  If aValue is not a special 
 object, the primitive will assign aValue to the SecurityDataSegment.  

 Generates an error if you do not have OtherPassword privilege.

 The primitive fails if you attempt to modify the password instance variable
 of the receiver's security data."

<protected primitive: 461>
self _primitiveFailed: #validatePassword: .
self _uncontinuableError
%

category: 'Private'
method: UserProfile
_newSecurityData

"Creates a new instance of UserSecurityData for the receiver."

" only called from UserProfileSet>>_add: "
<protected primitive: 462>

self _primitiveFailed: #_newSecurityData .
self _uncontinuableError
%

category: 'Accessing'
method: UserProfile
lastLoginTime

"Returns a DateTime of the last login time of the receiver, or nil if
 no login time has been recorded.  If no age limits are set on passwords,
 no login times are recorded.  (See updating methods in UserProfileSet.)

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

result := self _securityDataInstVarAt: 2 .
System _disableProtectedMode  "exit protected mode".
^ result
%

! lastLoginTime: only updated by primitives

category: 'Accessing'
method: UserProfile
lastPasswordChange

"Returns a DateTime that specifies when the password was last changed.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>
| result |
result := self _securityDataInstVarAt: 3 .
System _disableProtectedMode  "exit protected mode".
^ result
%

! lastPasswordChange: only updated by primitives

category: 'Accessing'
method: UserProfile
activeUserIdLimit

"Returns the maximum number of concurrent logins allowed for the receiver.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

result := self _securityDataInstVarAt: 4 .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Updating'
method: UserProfile
activeUserIdLimit: aPositiveInteger

"Sets the maximum number of concurrent logins for the receiver to be
 aPositiveInteger.  This change will take effect after this session commits.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

self _validatePrivilege.
aPositiveInteger ~~ nil ifTrue:[
  aPositiveInteger _isSmallInteger ifFalse:[
    System _disableProtectedMode  "exit protected mode".
    ^ aPositiveInteger _validateClass: SmallInteger .
    ].
  aPositiveInteger < 0 ifTrue:[
    System _disableProtectedMode  "exit protected mode".
    ^ aPositiveInteger _error: #rtErrArgNotPositive 
    ].
  ].
self _securityDataInstVarAt: 4 put: aPositiveInteger .
System _disableProtectedMode  "exit protected mode"
%

category: 'Accessing'
method: UserProfile
loginsAllowedBeforeExpiration

"Returns the number of logins allowed before the receiver's password will
 expire.  Zero or nil means unlimited logins.  A positive number is the number
 remaining before the password will expire.  A negative number means the account
 has expired.  The internal value is reset to zero when the password is
 changed.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

self _validatePrivilege.
result := self _securityDataInstVarAt: 5 .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Updating'
method: UserProfile
loginsAllowedBeforeExpiration: aPositiveInteger

"Sets the number of logins allowed using the receiver before the receiver's
 password will expire.  Zero means unlimited logins.  A positive number is 
 the number of logins to be allowed before the current password will expire.  
 The internal value is reset to zero when the password is next changed.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>
self _validatePrivilege.
aPositiveInteger ~~ nil ifTrue:[
  aPositiveInteger _isSmallInteger ifFalse:[
    System _disableProtectedMode  "exit protected mode".
    ^ aPositiveInteger _validateClass: SmallInteger .
    ].
  aPositiveInteger < 0 ifTrue:[
    System _disableProtectedMode  "exit protected mode".
    ^ aPositiveInteger _error: #rtErrArgNotPositive 
    ].
  ].
self _securityDataInstVarAt: 5 put: aPositiveInteger .
System _disableProtectedMode  "exit protected mode"
%

! isSpecialUserId also implemented in accountIsNeverDisabled() in pom.c
category: 'Private'
classmethod: UserProfile
isSpecialUserId: aUserId

 aUserId = 'SystemUser' ifTrue:[ ^ true ].
 aUserId = 'SymbolUser' ifTrue:[ ^ true ].
 aUserId = 'GcUser' ifTrue:[ ^ true ].
 aUserId = 'DataCurator' ifTrue:[ ^ true ].
 aUserId = 'Nameless' ifTrue:[ ^ true ].
 ^ false 
%

category: 'Accessing'
method: UserProfile
passwordNeverExpires

"Returns true if the receiver is an account that is immune from password
 expiration.  
 The SystemUser, SymbolUser, DataCurator, GcUser, and Nameless accounts are immune."

^ UserProfile isSpecialUserId: userId
%

category: 'Accessing'
method: UserProfile
isDisabled

"Returns true if logins for the receiver are disabled, false otherwise. 

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

result := (self _securityDataInstVarAt: 1 "isDisabled")  
	   _and:[ self _password41 == nil] .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Accessing'
method: UserProfile
reasonForDisabledAccount

"Returns the String that contains the reason why the receiver's password
 has been automatically disabled or expired. Some possible strings are:
  'LoginLimitReached'
     The account's password in no longer valid because its login limit
     was reached.  See UserProfile>>loginsAllowedBeforeExpiration.
  'LoginsWithInvalidPassword'
     The account is disabled due to an attempt to login to it too many
     times with an incorrect password.  See the stone configuration
     option STN_DISABLE_LOGIN_FAILURE_LIMIT.
  'PasswordAgeLimit'
     The account's password is no longer valid due to it being too
     old as determined by UserProfileSet>>passwordAgeLimit.
  'StaleAccount'
     The account is disabled due to it being too old
     as determined by UserProfileSet>>staleAccountAgeLimit.

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

result := self _securityDataInstVarAt: 6 .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Updating'
method: UserProfile
reasonForDisabledAccount: aString

"Updates the String that contains the reason why the receiver's password
 has been automatically disabled or expired.  

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

self _validatePrivilege.
(aString isKindOf: String) ifFalse:[
  System _disableProtectedMode  "exit protected mode".
  ^ aString _validateClass: String 
  ].
result := self _securityDataInstVarAt: 6 put: aString copy immediateInvariant .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Updating'
method: UserProfile
clearOldPasswords

"Clears the set of old passwords for the receiver, thus permitting reuse of
 some passwords that had been previously disallowed.  

 Generates an error if you do not have OtherPassword privilege."

<primitive: 901>

| result |

self _validatePrivilege.
result := self _securityDataInstVarAt: 7 put: Set new .
System _disableProtectedMode  "exit protected mode".
^ result
%

category: 'Updating Privileges'
method: UserProfile
_validatePrivilege

" You must have #OtherPassword to modify UserProfiles "

  System myUserProfile _validatePrivilegeName: #OtherPassword
%
