I am relatively need to DDD tactical patterns and need help modelling an example app im putting together. I am creating an app that is supposed to help people manage shifts in their stores/organizations.
I am currently working on my first microservice: Membership service (domain). This service is responsible for creating new organizations & inviting users to be become members of the created organizations. The following are rules of the service I am trying to model.
Any user can create an organization
A user can create more than one organization
The user that creates the organization is the initial admin of the organization
An organization should at least have 1 admin
Only admins can invite people to join the organization. They can invite people who are not current users by sending an invitation via email
A user is added to the organization only if they accept the invitation (probably click on a link via email)
So far, I only have 2 aggregate root classes: Organization & User
Organization
class Organization extends Aggregate {
private _id!: OrgId;
private _members!: Member[];
private _name!: Name;
get members() {
return this._members;
}
get id() {
return this._id;
}
constructor(args: InstantiateConstructorArgs | CreateConstructorArgs) {
super();
if (args.type === ConstructorTypes.Instantiate) {
this.loadFromHistory(args.events);
} else {
const organizationCreatedEvent = OrganizationCreatedEvent.create(
args.id,
args.creatorId,
args.name
);
this.applyChange(organizationCreatedEvent);
}
}
private applyOrganizationCreatedEvent(e: OrganizationCreatedEvent) {
const initialMember = new Member(
e.orgId,
MemberRoles.Admin,
e.creatorUserId
);
this._id = e.orgId;
this._name = e.orgName;
this._members = [initialMember];
}
}
As you can see, the Organization aggregate root is responsible for maintaining a collection of its members. It is event sourced as well. The Member class is defined within the context of the as such
export enum OrganizationMemberRoles {
Admin,
Regular,
}
class OrganizationMember {
constructor(
public readonly organizationId: OrgId,
public readonly role: OrganizationMemberRoles,
public readonly userId: UserId
) {}
}
User
class User extends Aggregate {
constructor(public readonly id: UserId) {
super();
}
createOrganization(orgId: OrgId, name: Name) {
return OrganizationFactory.CreateOrganization(orgId, this.id, name);
}
}
My Modelling Struggle
I am not sure what is the best way to model the invitation process. Since only Admins can extend an invitation to the organization, would you recommend creating an Admin class and putting the extendInvitation method on that? If so, how would the request flow look like?
Should I model the invitation outside of the Organization aggregate? Or should it be engulfed in the aggregate? If outside, then should I use an eventual consistency model to add the member to the organization?
The fact that only admin can create it is a "authorization" concern, maybe tomorrow you want to extend it to a subset of "special" users that are not admin of that org, or you want the admin of the entire platform to be able to invite a user in a specific org.
A user is added to the organization only if they accept the invitation (probably click on a link via email)
It seems to me that the invitation is an entity itself, and I see the possibility to have an "Invite" command on the Organization that can be execute only by admin of the org itself, and will create a new Invite (the email being sent is a side effect of the Invite entity being created).
(I see a good approach having the Invite insite the org as it should already have all the info needed in order to check the invariants, it knows all the users that already joined it)
Related
Im trying to pull the email addresses for all users within my org.
My GraphQL query looks like this:
{
organization(login: "####") {
membersWithRole(first: 100) {
totalCount
edges {
node {
login
name
email
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
However, it only shows the email if the user has made it publicly shown. The reason I need this is to remove users from my org that have left/cancelled.
Thanks,
it only shows the email if the user has made it publicly shown.
Yes, which means that private emails are not to be queried or, as explained here:
some users don't have an e-mail address defined in github.com/settings/profile and that causes the email field in the API response to be blank.
As asked before, using email alone won't work, since private emails would not be part of that kind of queries.
That can give you a User node is null if email is private or unset.
An other approach would be needed in your case, like, for instance, Get last activity for a user in an organisation GitHub Ecosystem.
i am trying to give default role as Internal/Subscriber to all users.
i made changes in we made changes in file /_system/config/apimgt/applicationdata/tenant-conf.json and added role such as to Internal/creator,Internal/everyone,apimrole
"Name": "apim:subscribe",
"Roles": "admin,Internal/creator,Internal/everyone,apimrole,Internal/subscriber"
it gives me below error
org.wso2.carbon.apimgt.api.APIManagementException: Error while adding the subscriber
laxman#gmail.com#carbon.super#carbon.super
any help appreciated
New user creation takes place in the WSO2 API Manager in two ways.
Through the management console of the API Manager
Self signup
In 1st way you can assign roles when creating users.
For self signed-up users there already exists a handler to assign Internal/subscriber role to the new users who are having Internal/selfsignup role.
To assign role: Internal/subscriber to new users or existing role not assigned users we have below two options:
Option 1
If you wish to assign subscriber role to existing role not assigned users using Management Console, then you can go to roles listing page there:
There is an option: Assign Users in Actions column in role list relevant to Internal/subscriber role.
It will list all the users who have not assigned Internal/subscriber role and there are several options to select many users at once and assign the role.
Option 2
You can write a custom user operation event listener and add it as OSGI bundle.
In this case you can refer this WSO2 IS doc and write a event listener extending AbstractIdentityUserOperationEventListener.
This sample code worked for me:
public class SampleEventListener extends AbstractIdentityUserOperationEventListener {
private static final String EVENT_LISTENER_TYPE = "org.wso2.carbon.user.core.listener.UserOperationEventListener";
private static final String SUBSCRIBER_ROLE = "Internal/subscriber";
#Override
public boolean doPreAddUser(String userName, Object credential, String[] roleList, Map<String, String> claims,
String profile, UserStoreManager userStoreManager) throws UserStoreException {
List<String> roles = new ArrayList<>(Arrays.asList(roleList));
if (!roles.isEmpty() && !roles.contains(SUBSCRIBER_ROLE)) {
userStoreManager.updateRoleListOfUser(userName, new String[]{}, new String[] { SUBSCRIBER_ROLE });
}
return true;
}
This will add Internal/subscriber role to each newly adding user, if the user doesn't have that role in the process of adding new user.
Here it has mentioned multiple interfaces with which you can implement User Store Listeners.
For OSGI bundle creation and deployment process you can find this sample GitHub project. You can copy the built jar file to the directory <APIM_HOME>/repository/components/dropins/ by following the steps have been mentioned there. (Since WSO2 API Manager is also using WSO2 IS components you can follow the same steps mentioned in README with the API Manger as well)
You can go through this blog post to get complete idea about OSGI bundling.
I'm developing two different applications, I will name them A and B.
A is an internet platform, where you can logon only if you have a valid user account.
B is an intranet platform, where users can authenticate via Active Directory. An administrator using application B should be able to create new user accounts for application A.
After the creation of a new user account, I want to be able to realize different functions, for example to send an e-mail to the registered mail address, so the new user can change the default password.
All the functionalities that I want to implement, can be done by the UserManager (see section "Use another app to add users" in the following link: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio#disable-register-page).
Based on this I implemented the following code:
public class ControllerClass : Controller
{
private readonly HelperClass _helper;
private readonly UserManager<IdentityUser> _userManager;
public ControllerClass (UserManager<IdentityUser> userManager)
{
_userManager = userManager;
_helper= new HelperClass (userManager);
}
}
public class HelperClass
{
private readonly DbContext _db;
private readonly UserManager<IdentityUser> _userManager;
public HelperClass (UserManager<IdentityUser> userManager)
{
_db = new DbContext ();
_userManager = userManager;
}
private async Task<string> EnsureUser(string userName, string userPassword)
{
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
user = new IdentityUser()
{
UserName = userName
};
await _userManager.CreateAsync(user, userPassword);
}
return user.Id;
}
internal async void CreateUser(UserVM uvm, int id)
{
var userId = await EnsureUser(uvm.userName, uvm.userPassword);
// TODO ...
}
}
Unfortunately I didn't manage to include the UserManager into my application B. I got the following error message: "An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[IdentityUser]' while attempting to activate 'ControllerClass '."
Do you have an idea, how I can add the UserManager to manage the users for another application?
Well, the specific error you're getting is simply because UserManager<TUser> is not registered in the service collection. In order to inject anything, you must first register it. In your actual user-facing app, that's being done by services.AddIdentity(). However, that does a lot more than just register UserManager<TUser>, so you shouldn't just run off and add the same command to your second app.
You could add a registration for it specifically:
services.AddScoped<UserManager<ApplicationUser>>();
However, it actually has a ton of dependencies, each of which would also need to be registered. If you look at the constructor, there's seven services not registered out of the box, many of which have their own dependency trees. Long and short, it's a pain. There's also the matter of separation of concerns, here. This would require adding in the whole data layer from the other app.
Your best bet is to simply expose an API on the Identity app (and lock it down, of course). That way, all the logic of working with users stays with the rest of that logic. The administration app, then, can call out to the API to add, update, delete, etc. users without having to have knowledge of how that's actually done.
Answering after 2 years. For future reader, You can use
services.AddIdentityCore<IdentityUser>();
which adds necessary services that are for user-management add/delete etc. without adding Login service.
to add EntityFramework you can create context and use like this
services.AddDbContext<ApplicationAuthDbContext>(options =>
{
// Configure the context to use postgresql.
options.UseNpgsql(config.GetConnectionString("AuthDbContext"))
.UseSnakeCaseNamingConvention();
});
services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<ApplicationAuthDbContext>();
For more information
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.identityservicecollectionextensions.addidentitycore?view=aspnetcore-6.0
Given the following Controller
namespace MyNamespace.Api.Controllers
{
[Authorize]
public class AccountController : ODataController
{
private Entities db = new Entities();
// GET odata/Account
[Queryable]
[ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Read", Resource = "Account")]
public IQueryable<Account> GetAccount()
{
return db.Accounts();
}
...
}
}
I override the ClaimsAuthorizationManager.CheckAccess(...)
public class AuthorizationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
var resource = context.Resource.First().Value;
var action = context.Action.First().Value;
return Policies.Validate(resource, action);
}
}
This is useful only to the point where I can check whether or not the Current Principal in general can Read Account. However, if I'd want to check which accounts a certain user is allowed to Read, I am lost.
Let's say I have a Manager user who should be able to read all Accounts for which he is a manager for whereas a non-manager user should be able to read only their own account.
Is there a best practice for this or have you done something like this previously and give me a few hints to look for?
I do not use ClaimsPrincipalPermissionAttribute because I cannot pass any dynamic parameters to it like requested Account from your sample.
Have a look at the book "Pro APS.NET Web API Security" page 97. They suggest to invoke AuthorizationManager from your controller action implementation by code new IdentityConfiguration().ClaimsAuthorizationManager.CheckAccess(context), where context is constructed manually so you can pass Account requested (for example) as Resource to check it in your AuthorizationManager implementation.
Also have a look at pluralsight training "Introduction to Identity and Access Control in .NET 4.5". There are also some info about how to implement claim-based security in Web API.
Now I am in progress of implementing the security you are talking about and I am interesting in the subject too.
My case is: role Administrator is assigned by Country, every Administrator can see entities only related to the countries they have access to.
UPDATE: After several projects I forgot about Claims-based security as this is extremely difficult way to make security checks. Today I use decorator pattern where all the security checks are done. It appears to be very easy to implement security even in OData Controllers like this:
public IQueriable MyQuriableEntitySet
{
get{ return implementationWithoutSecurity.MyQuriableEntitySet.Where(e=>e.Country.Code = currentUser.AssignedTo.CountryCode || currentUser.IsSuperAdmin); }
}
What i currently have is the following:
namespace AzureCCCMVC.Controllers
{
[Authorize(Roles="Admin")]
public class AdminController : Controller
{
//Stuff
}
}
what I want to do is have roles for each client such as
Roles { "DEMOAdmin", "GOOGAdmin" , "MSFTAdmin" }
and be able to Authorize The Client name (from URL) and in that role
I know I am doing a horrible job of explaining this... It is possible that I can have users that are users of several clients but only admin's of one ...
I am not sure if i understand exactly what you try to achive, but i guess you are heading in wrong direction. What would prevent you from just having roles independend from the client and store to which client an admin belongs to:
admin1 -> GOOG
admin2 -> MSFT
With this information just use [Authorize(Roles="Admin")] and show the user only data that belongs to his organisation:
[Authorize(Roles="Admin")]
public class AdminController : Controller
{
var data = GetDataForDomain(); //retreive data based on organisation of the user
}