Summary
[ Content Index | Application ]
Refer to Account Manager 4 for the latest version.
Account Manager is a drop-in library for adding quick support for multiple users and roles based security.
Refer to Vault for an example implementation.
Content Type: Application
Copyright Stephen W. Cote, 2000 - 2006.
Do not reproduce or distribute without the written consent of the author.
Index
Modification History
| Author | Date | Description |
| Stephen W. Cote | 12/03/2006 | Link to version 3.5 |
| Stephen W. Cote | 08/11/2006 | Link to version 3.2 |
| Stephen W. Cote | 06/15/2006 | Add new feature descriptions for version 3.1 |
| Stephen W. Cote | 04/01/2006 | Document created. |
Introduction
About
The AccountManager library is a drop-in solution for multi-organization and multi-user management. It defines its own user accounts and permission structure, and uses a generic methodology for defining groups and roles. AccountManager is in its third version, is a port from the original Java version. It was written to address a need for a lightweight directory service that could store account information across multiple organizations, user data, and complex permission relationships.
Annotations
- Using standard .NET 2.0 crypto libs, and Rijndael AES 192/256
- CoreLib, AccountManager, and integrated SecurityManager adapted from earlier Java version.
- Supports building under Mono, and MySQL in version 3.5
Dependencies
- .NET 2.0 or Mono Framework. (Not included).
- SQL Express, SQL Server, or MySQL on one or more computers. (Not included).
- DatabaseEngine library: (included in download) tool for managing SQL connections. This is a legacy library, and may not be used but is still required by AccountManager
- CoreLib: (included in download) an interface and utility library, including SecurityManager, ship key, product key ring, and signature key.
- AccountManager: (included in download) library for creating and managing multiple users, data, and permissions.
Download
- AccountManager_3.5.zip - (Includes AccountManager, CoreLib, DatabaseEngine, and dependencies)
- AccountManager_3.5.source.zip - Visual Studio 2005 project including AccountManager source code and dependent projects. Mono build instructions in make.bat files for each project.
Setup is very easy, though an understanding of the API and schema is necessary to use many of the features. By default, no configuration is necessary. In fact, all configuration files are hidden from view and don't need to be accessed for operating with all features. Simply check the setup state, specify default root and administration passwords as needed to complete setup, and that's it.
Schema and API
The schema organization is layed out around the following five concepts:
- Organizations are a top level descriptors in which everything is created.
- Accounts are individual user or system accounts.
- Controls are ubiquitous grouping and filtering schemas, and include generic controls, organizations permissions, groups, roles, and participants.
- Data is a generic schema describing binary data, and data-level permission mappings.
- Participations are a foreign-key schema between accounts, controls, organizations, permissions, groups, roles, participants, and data.
- The API is structured around the schema in the following manner. Note: The class names are the same from the original Java version.
- AccountManager provides access to the other APIs, some of which are restricted by permissions.
- AccountUsers manages queries for adding, removing, changing, and loading user accounts as AMUser objects.
- AccountSecurity manages login and logout requests, and requests for access to organization-level security managers.
- ControlType manages database requests for Controls.
- DataManager handles queries for adding, removing, changing, and loading data as AMData objects, requiring a valid AMUser object with sufficient permissions to perform the requested action.
- AccountFlags is a central utility API, and performs many of the initial participation mappings for account and data entries. It was named Flags because it was originally a helper for managing simple, generic account flags, and other utilities were eventually moved here.
- ProductBase is a convenience class for setting up and using AccountManager.
[ top ]
Participation Access Control
AccountManager uses Participation Access Control (PAC), an interpretation of Roles Based Access Control (RBAC). For comparison, here is a brief introduction to common access controls. Note that these are not necessarily independent of the other.
- Discretionary Access Control (DAC) defines access to objects at the discretion of the owner.
- Mandatory Access Control (MAC) extends or replaces Discretionary Access Control, and is a policy governing user access to objects they create, and where users may not change an assigned access level from the one applied by an administrator.
- Access Control List (ACL) describes privilege separation of a resource as related to a user or group.
- Roles Based Access Control (RBAC) is closer to an Access Control List (ACL), but differs in that privileges may be assigned at a higher level, such as a role level. RBAC may also be modelled to mimick a Lattice-Based Access Control (LBAC), which is a mathematical structure describing the greatest lower-bound and least upper-bound for a resource pair.
Participation Access Control (PAC) includes elements of RBAC, DAC, and ACL through resource participation. This could also be described as grouping or bucketing resources together.
A comparison of PAC to other Access Conttrols:
- PAC does not implement sensitivity labels, as is used by MAC.
- PAC is similar to DAC in that every object must have an owner, and it is different in that every object does not have to have an ACL.
- PAC is similar to RDAC in that permissions may be assigned to roles, and users may inherit permissions from those roles, and is different in that it does not expose generic constraints, only a few specific constraints related to accounts.
- PAC requires that every object belong to a group. In a filesystem data must understandably reside somewhere, unless it belong to a different filesystem not discoverable by the current one.
PAC has the following model:
- [source id](int) as [source type](int) participates with [target id](int) as [target type](int) in organization (id).
- It does not transcend and may not cross the organization level.
- A control, account, or data may participate with one another, so long as the type of participation is expected (ie: another participation, an organization, a control, a group, a role, a user, data, or a permission).
For common applications, the most often used APIs will be the AMUser and AMData objects, querying for lists of AMData objects, and querying for directory group identifiers.
Roles in PAC
Roles in PAC are achieved by creating the role and adding other controls, accounts, and data to that role. A control can be a generic controls, organizations permissions, groups, roles, and participants.
For example, to create a role that includes one or more accounts:
- Create the role, "Test Role"
- Add a participation of the account record id as type user to the role id as type role.
To enable that role to be able to perform permission "X" on Group "A":
- Create Role "Test Role" as above.
- METHOD 1:
- Create permission "X" for Group "A"
- Optionally create Control Group "B" as type control, and add participation of control group as type group to group as type group
- Add participation of permission "X" as type permission to group/control group as type group
- Add participation for Role "Test Role" to Group "A"/"B"
- METHOD 2
- Create permission "X" for Role "Test Role"
- Add participation of permission "X" as type permission to role "Test Role"
- Add participation of "Test Role" as role to group "X" as group
The logic in finding the permission in this case is to find the relevant participation types. For example, if testing to see if "Test Role" can read Group "A", then the two relevant participations are the read permission for Group "A", and the "Test Role" participation in that read permission. Depending on how the permissions are defined, it may only be possible to search directly for the permission instead of having to perform the second query.
Built-in Testing
The AccountManagerHelper class includes a SecurityTest method, which tests PAC security in the "development" organization using the "debug_user" account. once AccountManager is setup, no credentials are needed to run the test. Part of these tests verify the by-item ACLs, which is disabled by default for performance. So, when invoking this method, the feature is re-enabled temporarily and then restored to the original setting. This PAC test does not test permissions assigned at a role level, only permissions assigned to specific users for specific data items and specific groups. The following aspects are tested:
- login as "debug_user" in the "development" organization.
- create two normal user accounts, "secuser1" and "secuser2"
- create a "test" directory group under each account's home group.
- verify that "secuser1" can add data "testdata" to its "test" directory.
- verify that "secuser2" cannot add data to "secuser1"'s "test" directory.
- verify that "documentcontrol" cannot add data to "secuser1"'s "test" directory.
- verify that "secuser2" cannot read "testdata" data from "secuser1"'s directory
- add read permission to "secuser1"'s "testdata" specifically for "secuser2".
- verify that "secuser2" can read "testdata".
- remove read permission from "testdata" for "secuser2".
- add read permission to "secuser1"'s "test" directory specifically for "secuser2".
- verify that "secuser2" can read "testdata".
- Verify that "documentcontrol" can read "testdata". - this is true because the directory was created with public readability through documentcontrol.
- verify that "secuser2" cannot delete "testdata".
- verify that "secuser2" cannot change "testdata".
- add change permission to "testdata" for "secuser2".
- verify that "secuser2" can change "testdata".
- add delete permission to "secuser1"'s "test" directory for "secuser2".
- verify that "secuser2" can delete "testdata".
[ top ]
Setup
There are two ways to setup AccountManager: the easy way, and the hard way.
Easy Setup
Herein is a description of the steps, and then some example code in C#. Note: The class names are from the original Java version of AccountManager. For example: "ApplicationContext" is not the C# version, nor does it inheret from the C# version.
- Create a Core.ApplicationContext object. This is a generic application-scoped object used by AccountManager.
- Set the ConnectionString property on the new core.ApplicationContext object to point to an existing database, or SQLExpress database file.
- Set the UseDatabase property to true.
- Set the DatabaseType property to correspond with the expected database type. Supported types are SQL and MySQL.
- Set the HardenRSA property to true.
- Invoke the Initialize method.
- Invoke the InitializeRSA method.
- Create a new Core.Tools.AccountManager.ProductBase object, and include the new context object in the constructor.
- Check the IsSetup property to determine of the AccountManager database was setup. If not setup, it will need to be setup and an administration password and product account credentials may be specified (or the default may be used; but this isn't recommended for Web-based implementations).
- If the database is setup, invoke Initialize on the new ProductBase object, and, if not using the default product account, login to the product account.
Here is an example in C#, where the database file is assumed to be in the same directory as the application, and named core.mdf. Note that the account names and passwords should not be specified in this manner, and are only included for demonstration purposes.
Core.ApplicationContext app_context = new Core.ApplicationContext();
app_context.ConnectionString =
"Data Source=.\\SQLEXPRESS; AttachDbFileName="
+ app_context.StartupPath + "core.mdf;Integrated Security=True;"
+ "User Instance=True;Connection Timeout=300";
app_context.UseDatabase = true;
app_context.DatabaseType = Core.Data.DbFactory.CONNECTION_TYPE.SQL;
app_context.HardenRSA = true;
app_context.Initialize();
app_context.InitializeRSA();
Core.Tools.AccountManager.ProductBase product =
new Core.Tools.AccountManager.ProductBase(app_context);
if (product.IsSetup == false)
{
String sAdminPassword = "pasword123";
String sProductAccountName = "ProductUserName";
String sProductAccountPassword = "password123";
// don't hash these passwords
if(product.Setup(
sAdminPassword,
sProductAccountName,
sProductAccountPassword) == false){
// an error happened, can't continue;
}
}
if(product.IsSetup == true){
String sProductAccountName = "ProductUserName";
String sProductAccountPassword = "password123";
product.Initialize();
// Logging into a non-default account is optional at this point;
// the product user is a requirement only when using Improved security.
if (product.IsDefaultAccount == false &&
!product.LoginProductUser(
sProductAccountName,sProductAccountPassword
)){
// didn't login to the product account;
}
}
Hard Setup
Not much detail will be given on doing this the hard way, except to point out the differences:
- ProductBase isn't used, so AccountManager, DatabaseEngine, and DatabaseHelper must be setup and initialized individually.
- ApplicationContext doesn't use the database, and therefore logs are written to the filesystem and AccountManager must obtain connection objects from DatabaseEngine.
- Configuration files must be read from the filesystem.
- AccountManager "root" user must be created.
- "root" must create an "admin" user in the "public" organization.
- (optional) "admin" user in the "public" organization must be used to create a child organization for the product, which creates an "admin" user for the new organization.
- "admin" user in the public or the new product organization must be used to create new accounts.
Mono Build / Setup
Setting up Account Manager when using Mono is the same as when using .NET. However, the build instructions are slightly different. Instead of using a project, make.bat files are provided for each project folder. Execute the make.bat files in the following order:
- mysql-connector-net-1.0.7/make.bat (Use the Mono compiled MySql.Data.dll file; I've had a number of problems using a .NET build under Mono). However, projects may use the lib/MySql.Data.dll file for reference.
- CoreLib/make.bat (references lib/ZipLib.dll and lib/MySql.Data.dll)
- DatabaseEngine/make.bat (references CoreLib/CoreLib.dll, mysql-connector-net-1.0.7/MySql.Data.dll)
- AccountManager/make.bat (references CoreLib/CoreLib.dll, DatabaseEngine/DatabaseEngine.dll)
MySQL Setup
When using MySQL instead of SQL, set the ApplicationContext DatabaseType property to Core.Data.DbFactory.CONNECTION_TYPE.MYSQL.
To obtain a connection through the ApplictationContext instance, provide the connection string and database type to Core.Data.DbFactory.GetLocalConnection. There are a number of references to this throughout the code, and further example is beyond the scope of this article.
[ top ]
Accounts
The Product User Account
The Product User account is a normal user account used by ProductBase. This account is primarily exposed for using AccountManager in a single-user environment (ie: see Vault).
Login/Logout
User accounts are exposed as Core.Tools.AccountManager.AMUser objects. For non-web applications, three things are needed to login to an account:
- the account name
- the account password
- the organization in which the account belongs.
The following example shows how to login to an account, assuming that the easy setup was used and the ProductBase object was created. Note that the product account is logged in through product base in the previous example, so in this example a generic name will be used.
//
Core.Tools.AccountManager.ProductBase product_base = ...;
int organization_id =
product_base.AccountManagerHelper.getOrganizationId(
product_base.OrganizationName
);
String user_name = "TestUser";
// Must use the hashed password to login
String password_hash = Core.Util.Crypto.CryptoUtil.GetDigest("password123");
Core.Tools.AccountManager.AMUser user =
product_base.AccountManager.login(
user_name, password_hash, organization_id
);
// if user is null, then login failed
When using AccountManager with Web applications, a different method for logins is provided, and requires an additional parameter: the session id.
Core.Tools.AccountManager.AMUser user =
product_base.AccountManager.login(
session_id, user_name, password_hash, organization_id
);
After login for a Web application, a security identifier is exposed on the AMUser object. This identifier can be set to rotate on each request. A user object can only be recovered with both the session id and the security id.
Core.Tools.AccountManager.AMUser user =
product_base.AccountManager.getUserBySecurityId(
session_id, security_id
);
When retrieving the AMUser object using the session id, the account is subject to the session expiration stipulated on the AMUser object. By default, this is set to one hour. If the user idles longer than the expiration, AccountManager will logout the user and expire the internal session.
It is up to the implementation to determine whether an account login should be valid when the credentials are correct. For example, if an account has been frozen, it is up to the implementation to render the login invalid.
Creating New Accounts
New users are created with Account Administrators. By default, the only account administrator for an organization is the "admin" account, and when using the ProductBase, the "admin" account password is the same as the "root" password. Also, accounts start out as generic, and may then be cast upwards to be a normal account and user accounts. This allows internal accounts to be created, such as the documentcontrol account, that would fail a check for normalcy, but that may nonetheless participate in the PAC. For example, since a valid account is required to access any data, documentcontrol is used to anonymously read public data. It is up to the implementation to check that an account is normal and is a user account (AMUser.getIsAccountNormal and AMUser.getIsUserAccount), or whether or not the account has been frozen (AMUser.getIsFrozen).
Core.Tools.AccountManager.ProductBase product_base = ...;
// Since product user is here, grab the organization from that account;
// or see previous example for looking up the organization id by name.
int organization_id = product_base.ProductUser.getOrganizationId();
String admin_password_hash =
Core.Util.Crypto.CryptoUtil.GetDigest("password123");
Core.Tools.AccountManager.AMUser admin_user =
product_base.AccountManager.login("admin", password_hash, organization_id);
String new_user_name = "TestUser";
String new_password_hash =
Core.Util.Crypto.CryptoUtil.GetDigest("password123");
Core.Tools.AccountManager.AMUser new_user =
product_base.AccountManager.createGenericAccount(
admin_user, new_user_name, new_password_hash, organization_id
);
product_base.AccountManager.getAccountFlags().setAccountDefaults(
product_user, new_org_id, true, true, true
);
Additional account data such as the persons name, address, email, web site, phone number, etc, may be specified on the AMUser object, and updated through AccountManager.
Obtaining and Deleting Accounts
Accounts can be retrieved without a password by an Account Administrator using the AccountManager.getAccount method. The Account Administrator can also update the account, such as changing the password, or delete the account once the account object has been obtained. Only Account Administrators may delete accounts, and an account may not delete itself. When an account is deleted, all data created by that account is may also be deleted. If the data is deleted, then all groups and PAC references owned by the account are also removed.
The following example shows how to find and delete the previously created user, specifying to delete any data as well.
Core.Tools.AccountManager.ProductBase product_base = ...;
// Since product user is here, grab the organization from that account;
// or see previous example for looking up the organization id by name.
int organization_id = product_base.ProductUser.getOrganizationId();
String admin_password_hash = Core.Util.Crypto.CryptoUtil.GetDigest("password123");
Core.Tools.AccountManager.AMUser admin_user =
product_base.AccountManager.login(
"admin", password_hash, organization_id
);
Core.Tools.AccountManager.AMUser user =
product_base.AccountManager.getAccount(
admin_user, "TestUser", organization_id
);
product_base.AccountManager.deleteAccount(admin_user,user, true);
[ top ]
Data and Groups
Data in AccountManager is owned by an account, belongs to a group, must have a unique name in that group, must specify a mime-type, and may not be empty (zero bytes). By default, data is always encrypted with the organization keys, and may be further encrypted with a password cipher associated with the user account, and also enciphered by an improvement (Improvements are discussed in detail in the Vault section).
Organization level encryption happens behind the scenes and is controlled by enabling or disabling encryption on the AccountManager instance. In a standalone application where both the database and application are accessible, Organization level encryption provides little security. However, in a Web application environment, full access to both the database and the application binaries are required to decipher the data.
Account level ciphers must be set by the implementation. For example ,the ProductBase class sets a data cipher on the product user account based on a salted hash of the plaintext password. When data is being created, the cipher must also be set on the AMData object as well as specifying that the data is to be enciphered. However, when Data is loaded from the database, any cipher specified on the user account is copied to the data item, and if the data is enciphered, that cipher is used to attempt to decipher the data. If it fails, the data is thrown out. The reason for the extra effort in specifying the cipher is that unlike the organization level encryption and the vault encryption, no record is kept about which cipher was used, just a bit indicating the data is or is not enciphered.
Vault encryption through improved security is discussed in the Vault document.
Directory Groups
Creating data is straightforward. Finding out where to put the data can be a trickier, so the groups will be addressed first. Data is stored in directory groups (or, a group of type directory). The easiest way to describe the grouping structure is to consider a Linux filesystem. Each normal user account has its own group, "{user name}", whose parent is "{home}", and each account has permission to write to its "{user name}" group. Group identifiers can be obtained by using the home directory group id as a base, and then retrieving the group identifiers for specific names while providing the parent references. Or, group identifiers may also be obtained by using a pseudo path structure. Consider the following example.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
int home_dir_id = product.AccountManager.getUserDirectoryGroupId(user);
// is the same as:
int home_dir_id2 =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "~", user.getOrganizationId()
);
// is the same as:
int home_dir_id3 =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "/home/" + user.getUserName(), user.getOrganizationId()
);
Now consider adding a new group, "My Personal Stuff" to the home directory, and then retrieving the id:
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
// Get the type id for a directory in the user's organization
//
int group_type_id =
product.AccountManager.getDirectoryGroupTypeId(user.getOrganizationId());
// Get the user's home directory to use as the parent
//
int home_dir_id =
product.AccountManager.getUserDirectoryGroupId(user);
// Add the group as the directory type, and where the home directory is the parent.
product.AccountManagerHelper.addGroup(
user, "My Personal Stuff",
group_type_id, home_dir_id, user.getOrganizationId()
);
// Now, get the id
int new_dir_id =
product.AccountManager.getDirectoryGroupId(
"My Personal Stuff", home_dir_id, user.getOrganizationId()
);
// Or, use the path structure
int new_dir_id2 =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "~/My Personal Stuff", user.getOrganizationId()
);
Data is added to a group, or moved to another group, by specifying the group id on the AMData object. A valid group id is required to add the data to the database.
// Obtain a new data object from AccountManager
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
// Get the group id to which the data will be added
//
int dir_id =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "~/My Personal Stuff", user.getOrganizationId()
);
// Data is specified in bytes, so convert the example string
//
String test_string = "This is some test data.";
byte[] test_bytes = Core.Util.Lang.LangUtil.StringToByteArray(test_string);
// A valid user is required to obtain the new data object
Core.Tools.AccountManager.AMData new_data =
product.AccountManager.getNewData(user);
// Set the mimetype
new_data.setMimeType("text/plain");
// Set the name
new_data.setName("Example Data");
// Set the group id
new_data.setGroupId(dir_id);
// Set the bytes
new_data.setBytes(test_bytes);
// Add the data to the database
bool succeeded = product.AccountManager.addNewData(user, new_data);
After adding data, the next step is to understand how to discover AMData objects that a user is able to access. This starts with learning how to access group lists. The following example shows how to obtain a list of all directory groups in the user's home directory.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
// Obtain a reference to a starting location; the user's home directory
int home_dir_id =
product.AccountManager.getUserDirectoryGroupId(user);
// Get the group type identifier to filter the results
int dir_type =
product.AccountManager.getDirectoryGroupTypeId(
product.ProductUser.getOrganizationId()
);
// Obtain the list of groups. As mentioned earlier, groups are a type of control.
Core.Tools.AccountManager.Control[] list =
product.AccountManager.getGroupList(
dir_type, home_dir_id, user.getOrganizationId()
);
for(int i = 0; i > list.Length; i++){
// with this id, all of the subdirectories can be accessed; repeat ad nauseum
int sub_dir_id = list[i].getControlId();
int sub_dir_name = list[i].getControlName();
}
The next important step is understanding how to obtain data lists. AMData objects can be retrieved with and without actual data. This is important because obtaining a data list of thousands of items would be incredibly slow if all of the actual data had to be extracted. Instead, AMData can be retrieved in details only mode, which includes all other properties except the actual data.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
int dir_id =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "~/My Personal Stuff", user.getOrganizationId()
);
// Get a list of data details. The actual data isn't included.
Core.Tools.AccountManager.AMData[] data_list =
product.AccountManager.getDataList(user, dir_id);
Once a data id or data name is known, and the location of the data is known, the data can easily be recovered.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
int dir_id =
product.AccountManager.getAccountFlags().findDirectoryId(
user, "~/My Personal Stuff", user.getOrganizationId()
);
String data_name = "Example Data";
// Get the data, in this example, using the overload to specify
// not to fetch the details only, but to get everything
Core.Tools.AccountManager.AMData data =
product.AccountManager.getData(user, data_name, dir_id, false);
// Extract the data bytes, and retrieve the original string value
String test = Core.Util.Lang.LangUtil.ByteArrayToString(data.getBytes());
Using a cipher to add additional encryption to the data is very straight forward.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
int dir_id = ...
byte[] test_bytes = ...;
// The cipher can be set as a string, but is stored interally as a byte array
user.setCipherKey(product.GetSaltedCipher("some plain text"));
Core.Tools.AccountManager.AMData new_data =
product.AccountManager.getNewData(user);
new_data.setMimeType("text/plain");
new_data.setName("Example Data");
new_data.setGroupId(dir_id);
// Set the cipher settings before setting the bytes
new_data.setEnciphered(true);
// Note that the cipher is not automatically set on the data object
new_data.setCipherKey(user.getCipherKey());
// Now, when the bytes are set, they are enciphered
new_data.setBytes(test_bytes);
Retrieving enciphered data is almost exactly the same as the previous example of retrieving a data object, except the cipher key must be set on the user account before that account is used to fetch the data. When retrieving the data, the cipher key is automatically copied over to the data object, whereas this is a manual task when setting the cipher key.
Deleting data is similar to deleting an account. The AMData object to be deleted is specified along with a user who has permission to delete the object. Deleting groups, on the other hand, also requires invoking the AccountFlags.clearControlCache after the group is deleted to reset in-memory references to the child groups and data.
Data permissions may be set directly on the data object (by-object permissions), or are otherwise inherited from the parent group or any other PAC references. By-object permissions are enabled by setting DataManager.ByItemPermissions to true.
[ top ]
Flags and Tags
From it's earliest conception, the AccountManager library used a Flag concept to define simple or universal permissions.
In terms of the Participation Access Control system in Account Manager, a Flag is a permission of type flags and is applicable to users.
Version 3.1 introduced the data_flags permission, and, as the name implies, is applicable to data.
Flags are created at an organizational level.
User flags can only be added by an account administrator, while data flags can be added by anyone.
This setup makes it easy for an account administrator to define and optionally set a flag for a user,
and then control who may add or remove data flags.
Version 3.1 makes it easy to define data flags, which is another way of describing a tag.
Tagging content allows for similar content to be alternately grouped based on the application of one or more tags.
The only thing needed to create a flag is a name. Consider the following example of creating a new flag with an account administrator, and setting that flag on another user:
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser admin_user = ...;
Core.Tools.AccountManager.AMUser user = ...;
// create the new flag
product.AccountManager.getAccountFlags().addFlag(
admin_user,
"CanCreateDataFlags",
admin_user.getOrganizationId()
);
// set the flag
product.AccountManager.getAccountFlags().setIsFlag(
admin_user,
user,
"CanCreateDataFlags",
admin_user.getOrganizationId()
);
The following example shows how the user flag is checked prior to attempting to create a data flag, and then setting the data flag on a data object.
Core.Tools.AccountManager.ProductBase product = ...;
Core.Tools.AccountManager.AMUser user = ...;
Core.Tools.AccountManager.AMData data = ...;
bool is_flag = product.AccountManager.getAccountFlags().getIsFlag(
user,
"CanCreateDataFlags",
user.getOrganizationId()
);
// check if the user can create data flags
if(is_flag){
product.AccountManager.getAccountFlags().addDataFlag(
user,
"Cool Blog Item",
user.getOrganizationId()
);
}
// Add the flag to a data object
// User must own the data object to set a flag
product.AccountManager.getAccountFlags().setIsFlag(
user,
data,
"Cool Blog Item",
user.getOrganizationId()
);
// Get whether the data is flagged.
bool is_data_flag = product.AccountManager.getAccountFlags().getIsFlag(
data,
"Cool Blog Item",
user.getOrganizationId()
);
// Get a list of all flags applied to the data
// these are participation ids
int[] data_flags = product.AccountManager.getAccountFlags().getIsFlags(
data,
user.getOrganizationId()
);
// Get a list of all data (the last bool is for details only)
// that matches a specified list of flags
AMData[] data_list = product.AccountManager.getDataListByFlag(
user,
new String[]{"Cool Blog Item"},
user.getOrganizationId(),
true
);
[ top ]