Introduction
In the last article we made real modifications to Cerberus with the SOAP API. It may not seem like much to create a single user, but it was an important step in the path to understanding the API.
This time we'll make modifications to Cerberus Group objects. We will create a group, modify its members, and modify the virtual directories it grants to members. We also explain interactions between groups and users that determine a user's effective constraints.
Running Example-GroupManipulation.ps1
Once again, we've provided sample code to demonstrate the changes we'll be making. If you've followed the previous guides closely, you'll recognize similar patterns and even some duplicate code.
We strongly recommend running this script in a test environment, not on a production Cerberus instance.
- Download Example-GroupManipulation.ps1
- Open a PowerShell console and change directory to the script location
- Invoke Unblock-File and/or modify execution policy
- Run the script
Use -EnableTls12 and -DisableCertValidation flags, if necessary
Supply -WSDLUrl parameter if Cerberus is on a different host
Supply -CerberusServiceUrl if running Cerberus 10.0.9 or earlier
For example:
& .\Example-GroupManipulation.ps1 -EnableTls12 -DisableCertValidation
- Provide Cerberus primary admin credentials when prompted
If successful, something like this will appear in the console:
PS C:> & .\Example-GroupManipulation.ps1 -EnableTls12 -DisableCertValidation
Windows PowerShell credential request.
Provide master admin credentials for Cerberus FTP Server
User: Admin
Password for user Admin: *****************
Successfully created group PsSOAPTestGroup
Successfully added groupRoot to PsSOAPTestGroup
Retrieved PsSOAPTestGroup
Successfuly modified PsSOAPTestGroup
Successfully retrieved list of groups
PsSOAPTestGroup
PsSOAPTestGroup exists in the list of groups
Successfully created user PsSOAPTestUser
Successfully found PsSOAPTestUser
Successfully made PsSOAPTestUser a member of PsSOAPTestGroup
Successfully retrieved PsSOAPTestUser
Group-allowed protocols now overridden by user-allowed protocols
Successfuly retrieved PsSOAPTestUser
Successfully removed PsSOAPTestUser from PsSOAPTestGroup
Successfully deleted PsSOAPTestUser
Successfully deleted PsSOAPTestGroup
As you can see, this script creates a group, user, modifies a few of their properties, shuffles the user in and out of the group, and deletes them both.
Code Walk-Through
Group manipulation looks similar to User manipulation since, after all, Groups and Users share many of the same properties (allowed protocols, permissible IPs, virtual directory lists, for example). AddGroup is invoked for creating and updating groups and AddDirectoryToGroup is invoked for modifying virtual directories on groups.
This script uses a few PowerShell idioms to reduce verbosity in areas we've already covered. We'll call these out during the walk-through.
Setup SOAP Connection
This section should look quite familiar by now; it is quite similar to the sections in HelloCerberus.ps1 and Example-UserManipulation.ps1.
The one change is the creation of a hash table containing Cerberus credentials. We'll reuse this object to initialize request objects later in the script. We make thirteen requests to the Cerberus SOAP API, so individually initializing the credentials on every request begins to add up.
# Hashtable containing credentials for later request object population
$requestWithCreds = @{
credentials = @{
user = $CerberusCredentials.UserName
password = $CerberusCredentials.GetNetworkCredential().Password
}
}
Create a New Group
Similar to dealing with users, we create a new object, populate the relevant properties and invoke an Add operation to create it within Cerberus FTP Server.
# The name of the test group
$newTestGroupName = "PsSOAPTestGroup"
# Create new Group object
[CerberusFtp.Group] $newGroup = New-Object -TypeName CerberusFtp.Group
$newGroup.name = $newTestGroupName
$newGroup.desc = "New Test Cerberus Native Group from PowerShell"
We set isSimpleDirectoryMode and protocols to allow only https. Later we'll demonstrate how to override these on a per-user basis.
$newGroup.isSimpleDirectoryMode = $true
$newGroup.isSimpleDirectoryModeSpecified = $true
$newGroup.protocols = New-Object -TypeName CerberusFtp.ProtocolsAllowed
$newGroup.protocols.https = $true
Finally, create the addGroupRequest object, invoke the AddGroup operation and check the result. This time, when we create the CerberusFtp.AddGroupRequest object, we initialize its credentails with the $requestWithCreds variable we declared earlier:
# Create addGroupRequest object
[CerberusFtp.AddGroupRequest] $addGroupRequest = New-Object -TypeName CerberusFtp.AddGroupRequest
$addGroupRequest = $requestWithCreds
$addGroupRequest.Group = $newGroup
# Issue the AddGroup request
[CerberusFtp.AddGroupResponse] $addGroupResponse = $CerberusSvc.AddGroup($addGroupRequest)
# Check response for success or failure
if (-not $addGroupResponse.result){
Write-Error "Failed to create group: $($addGroupResponse.message)"
} else {
Write-Host "Successfully created group $newTestGroupName"
}
Add Virtual Directory to a Group
Again, similar to adding virtual directories to users, but a different operation is provided for groups:
# Create a new AddDirectoryToGroupRequest object
[CerberusFtp.AddDirectoryToGroupRequest] $addDirectoryRequest = $requestWithCreds
$addDirectoryRequest.groupName = $newTestGroupName
$addDirectoryRequest.directory = New-Object -TypeName CerberusFtp.VirtualDirectory
# Populate virtual directory object with name, path, and permissions
$addDirectoryRequest.directory.name = "groupRoot"
$addDirectoryRequest.directory.path = "c:\groupRoot"
$addDirectoryRequest.directory.permissions = New-Object -TypeName CerberusFtp.DirectoryPermissions
# Grant download, upload, list files, list directories, rename files, rename directories,
create, and delete files, delete directories
$addDirectoryRequest.directory.permissions.allowDownload = $true
$addDirectoryRequest.directory.permissions.allowDownloadSpecified = $true
$addDirectoryRequest.directory.permissions.allowUpload = $true
$addDirectoryRequest.directory.permissions.allowListDir = $true
$addDirectoryRequest.directory.permissions.allowListFile = $true
$addDirectoryRequest.directory.permissions.allowRename = $true
$addDirectoryRequest.directory.permissions.allowRenameSpecified = $true
$addDirectoryRequest.directory.permissions.allowDirectoryCreation= $true
$addDirectoryRequest.directory.permissions.allowDelete= $true
$addDirectoryRequest.directory.permissions.allowDeleteSpecified = $true
# Issue the AddDirectoryToGroup request
[CerberusFtp.AddDirectoryToGroupResponse] $addDirectoryResponse = $CerberusSvc.AddDirectoryToGroup($addDirectoryRequest)
# Check response for success or failure
if (-not $addDirectoryResponse.result){
Write-Error "Failed to add virtual directory to group: $($addDirectoryResponse.message)"
} else {
Write-Host "Successfully added $($addDirectoryRequest.directory.name) to $newTestGroupName"
}
Modify Group Description
Modifying a Group object is a compound operation. Retrieve the existing group object with GetGroupInformation, make modifications, then overwrite the existing group with AddGroup:
[CerberusFtp.GetGroupInformationRequest] $getGroupRequest = $requestWithCreds
$getGroupRequest.name = $newTestGroupName
[CerberusFtp.GetGroupInformationResponse] $getGroupResponse = $CerberusSvc.GetGroupInformation($getGroupRequest)
if (-not $getGroupResponse.result) {
Write-Error "Failed to retrieve group: $($getGroupResponse.message)"
} else {
Write-Host "Retrieved $newTestGroupName"
$existingGroup = $getGroupResponse.group
$existingGroup.desc = "This group was created for demonstration purposes in PowerShell"
[CerberusFtp.AddGroupRequest] $modifyGroupRequest = $requestWithCreds
$modifyGroupRequest.Group = $existingGroup
[CerberusFtp.AddGroupResponse] $modifyGroupResponse = $CerberusSvc.AddGroup($modifyGroupRequest)
if(-not $modifyGroupResponse.result){
Write-Error "Failed to modify group: $($modifyGroupResponse.message)"
} else {
Write-Host "Successfuly modified $($existingGroup.name)"
}
}
List Current Groups
A list of all current group names can be retrieved with GetGroupList.
Because the GetGroupListRequest object contains only credentials and no other request data, we can get away with passing the $requestWithCreds hash table as a short-cut. PowerShell manages the conversion of the hash table to a GetGroupListRequest object transparently.
[CerberusFtp.GetGroupListResponse] $getGroupListResponse = $CerberusSvc.GetGroupList($requestWithCreds)
if (-not $getGroupListResponse.result){
Write-Error "Failed to retrieve group list: $($getGroupListResponse.message)"
} else {
Write-Host "Successfully retrieved list of groups"
Write-Output $getGroupListResponse.GroupList
if ($getGroupListResponse.GroupList -contains $newTestGroupName){
Write-Host "$newTestGroupName exists in the list of groups"
} else {
Write-Host "$newTestGroupName was not found in the list of groups"
}
}
Create New User
This time we create the User object with a nested hash table. This does exactly what you'd expect; names and values in the hash table populate the CerberusFtp.User object. PowerShell will emit an error message if required properties are missing and if unexpected properties are found.
$newTestUserName = "PsSOAPTestUser"
[CerberusFtp.User] $newUser = @{
name = $newTestUserName
password = @{value = "TestPasswordChangeImmediately1234!@#$"}
desc = "This user is for testing group membership modifications"
isDisabled = @{value = $true; valueSpecified = $true}
}
[CerberusFtp.AddUserRequest] $addUserRequest = $requestWithCreds
$addUserRequest.User = $newUser
[CerberusFtp.AddUserResponse] $addUserResponse = $CerberusSvc.AddUser($addUserRequest)
if (-not $addUserResponse.result){
Write-Error "Failed to create user: $($addUserResponse.message)"
} else {
Write-Host "Successfully created user $newTestUserName"
}
Add User to Group
There are some surprises in Cerberus' model of the group-user relationship:
- Cerberus Users carry a reference to the Group they're a member of.
This is a departure from typical identity systems, where group objects contain references to their members. - The user property named named "groupList" stores the primary group reference.
It is an array, accepting many group names, but only the last in the list is evaluated for user constraints and virtual directories. - The user property named "secondaryGroupList" stores the list of secondary group references.
Secondary groups only add virtual directories to the user. User constraints, such as allowed IP and allowed protocols are only inherited from the primary group.
Unlike the groupList, secondaryGroupList accepts the entire array of group names and adds them to the user. - When using SOAP API to add users to groups, group properties are not automatically inherited by the user.
The GUI Admin tools perform this step for you, whereas in SOAP API, your script must perform this step, if desired.
As we add the user to the group, we'll demonstrate how all of the above surprises affect our script.
Since we are modifying an existing user, we first retrieve the current user object:
[CerberusFtp.GetUserInformationRequest] $userInfoRequest = $requestWithCreds
$userInfoRequest.userName = $newTestUserName
[CerberusFtp.GetUserInformationResponse] $existingUserResponse = $CerberusSvc.GetUserInformation($userInfoRequest)
if (-not $existingUserResponse.result){
Write-Error "Failed to find user $newTestUserName : $($existingUserResponse.message)"
} else {
Write-Host "Successfully found $newTestUserName"
$existingUser = $existingUserResponse.UserInformation
In PowerShell, @() signifies an array and @{} signifies a hash table. So the next line creates an array containing a single hash table containing a 'name' property, whose value is the group name:
$existingUser.groupList = @(@{name=$newTestGroupName})
Adding a secondary group is nearly the same (see the script source for creation of the secondary group):
$existingUser.secondaryGroupList = @(@{name=$newTestSecondaryGroupName})
There are twelve user properties that may be inherited through group membership. This bit of code sets them all to defer to the group's value. We'll cover the details of this "priority" attribute in the next section.
foreach ($propName in @( "authMethod"
"disableAfterTime"
"ipAllowedList"
"isAllowPasswordChange"
"isAnonymous"
"isDisabled"
"isSimpleDirectoryMode"
"maxLoginsAllowed"
"maxUploadFilesize"
"protocols"
"requireSecureControl"
"requireSecureData")
) {
$existingUser.$propName.priority = "group"
$existingUser.$propName.prioritySpecified = $true
}
The rest is a typical user update with an invocation of AddUser:
[CerberusFtp.AddUserRequest] $modifyUserRequest = $requestWithCreds
$modifyUserRequest.User = $existingUser
[CerberusFtp.AddUserResponse] $modifyUserResponse = $CerberusSvc.AddUser($modifyUserRequest)
if (-not $modifyUserResponse.result){
Write-Error "Failed to update exiting user: $($modifyUserResposne.message)"
} else {
Write-Host "Successfully made $newTestUserName a member of $newTestGroupName"
}
}
Override Allowed Protocols for Group Member
In the last section, we added the test user to a group and set their properties to "group" priority. We'll explain a little more about what this means and then walk though the code that changes the priority of a user property to override the group value.
Our existing Group Documentation briefly explains this feature of Cerberus FTP Server.
Essentially, Cerberus tags some user properties as group-inherited or user-overridden values. The switch is is expressed intuitively in the User Manager GUI with a toggle button and symbols for each state:
In SOAP API, this is expressed as a priority attribute on each property, which may be set to "group" or "user" accordingly. The attribute defaults to "user" for newly created users.
For instance, the CerberusFtp.ProtocolsAllowed type appears like this in PowerShell. Note the various protocols which may be allowed, plus the a priority attribute:
PS C:\> CerberusFtp.ProtocolsAllowed].declaredProperties |Select-Object name, propertyType
Name PropertyType
---- ------------
priority CerberusFtp.UserPropertyPriority
prioritySpecified System.Boolean
ftp System.Boolean
ftps System.Boolean
sftp System.Boolean
http System.Boolean
https System.Boolean
The override takes place on the User object, so we first retrieve our user from Cerberus FTP Server:
[CerberusFtp.GetUserInformationRequest] $getUserRequest = $requestWithCreds
$getUserRequest.userName = $newTestUserName
[CerberusFtp.GetUserInformationResponse] $getUserResponse = $CerberusSvc.GetUserInformation($getUserRequest)
if (-not $getUserResponse.result){
Write-Error "Unable to retrieve user: $($getUserResponse.message)"
} else {
Write-Host "Successfully retrieved $($getUserResponse.UserInformation.name)"
We want this user to have both FTPS and HTTPS access, regardless of the constraints set by their group. We allow these protocols by setting them to $true on the user protocols property, then enable the override by setting priority to "user" and prioritySpecified to $true:
$existingUser = $getUserResponse.UserInformation
$existingUser.protocols.ftps = $true
$existingUser.protocols.https = $true
$existingUser.protocols.priority = "user"
$existingUser.protocols.prioritySpecified = $true
And now we push the modified user object with AddUser:
[CerberusFtp.AddUserRequest] $modifyUserRequest = $requestWithCreds
$modifyUserRequest.User = $existingUser
[CerberusFtp.AddUserResponse] $modifyUserResponse = $CerberusSvc.AddUser($modifyUserRequest )
if (-not $modifyUserResponse.result){
Write-Error "Unable to update user: $($modifyUserResponse.message)"
} else {
Write-Host "Group-allowed protocols now overridden by user-allowed protocols"
}
}
Remove User from Group
As previously mentioned, group membership is stored with the User object, so removing a user from their group means making a change to their groupList property.
[CerberusFtp.GetUserInformationRequest] $getUserRequest = $requestWithCreds
$getUserRequest.userName = $newTestUserName
[CerberusFtp.GetUserInformationResponse] $getUserResponse = $CerberusSvc.GetUserInformation($getUserRequest)
if (-not $getUserResponse.result){
Write-Error "Failed to retrieve user: $(getUserResponse.message)"
} else {
Write-Host "Successfuly retrieved $($getUserResponse.UserInformation.name)"
$existingUser = $getUserResponse.UserInformation
if ($existingUser.groupList.Count -lt 1){
Write-Error "Cannot remove user from group; user is not a member of any group"
} else {
We empty the groupList property by assigning it an empty array:
$previousMembership = $existingUser.groupList
$existingUser.groupList = @()
This bit of code sets all of the twelve user properties that may be inherited through group membership to defer to the user's value.
foreach ($propName in @( "authMethod"
"disableAfterTime"
"ipAllowedList"
"isAllowPasswordChange"
"isAnonymous"
"isDisabled"
"isSimpleDirectoryMode"
"maxLoginsAllowed"
"maxUploadFilesize"
"protocols"
"requireSecureControl"
"requireSecureData")
) {
$existingUser.$propName.priority = "user"
$existingUser.$propName.prioritySpecified = $true
}
Then we push the modified user with AddUser and display feedback to the host console:
[CerberusFtp.AddUserRequest] $modifyUserRequest = $requestWithCreds
$modifyUserRequest.User = $existingUser
[CerberusFtp.AddUserResponse] $modifyUserResponse = $CerberusSvc.AddUser($modifyUserRequest)
if (-not $modifyUserResponse.result){
Write-Error "Failed to update exiting user: $($modifyUserResponse.message)"
} else {
Write-Host "Successfully removed $newTestUserName from $($previousMembership.name -join ', ')"
}
}
}
Delete User
Clean up our test user before we're done.
[CerberusFtp.DeleteUserRequest] $deleteUserRequest = $requestWithCreds
$deleteUserRequest.user = $newTestUserName
[CerberusFtp.DeleteUserResponse] $deleteUserResponse = $CerberusSvc.DeleteUser($deleteUserRequest)
if (-not $deleteUserResponse.result){
Write-Error "Failed to delete user: $($deleteUserResponse.message)"
} else {
Write-Host "Successfully deleted $newTestUserName"
}
Delete Group
Finally, we delete the group.
[CerberusFtp.DeleteGroupRequest] $deleteGroupRequest = $requestWithCreds
$deleteGroupRequest.name = $NewTestGroupName
[CerberusFtp.DeleteGroupResponse] $deleteGroupResponse = $CerberusSvc.DeleteGroup($deleteGroupRequest)
if (-not $deleteGroupResponse.result){
Write-Error "Failed to delete group: $($deleteUserResponse.message)"
} else {
Write-Host "Successfully deleted $NewTestGroupName"
}
A group cannot be deleted if it still has members. You'll receive an error message like this one if you try:
result message
------ -------
False The following accounts are still members of this group: PsSOAPTestUser
Conclusion
There are many more Cerberus API operations available beyond user and group management. They deal with server configuration, listener management, and backup/restore, to name a few. At this point, you should have all the tools and techniques necessary to explore these operations on your own.
To round out this introduction, the last article will provide an overview of entire Cerberus SOAP API grouped by functional domain. We'll also identify operations we recommend avoiding, as they're primarily for internal use.
Please leave feedback in comments! We would love to hear what you're doing with Cerberus SOAP API, whether the guides were relevant to your work, and what we can do to improve both the guides and the API itself.
Comments
0 comments
Please sign in to leave a comment.