diff --git a/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.Designer.cs b/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.Designer.cs new file mode 100644 index 00000000..2247e693 --- /dev/null +++ b/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.Designer.cs @@ -0,0 +1,2834 @@ +// +using System; +using CcsSso.DbPersistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace CcsSso.Core.DbMigrations.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId")] + partial class Add_UserAccessRolePending_OrganisationUserGroupId + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.10") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Application") + .HasColumnType("text"); + + b.Property("Device") + .HasColumnType("text"); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("EventTimeUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("IpAddress") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("AuditLog"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.AutoValidationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AssignToAdmin") + .HasColumnType("boolean"); + + b.Property("AssignToOrg") + .HasColumnType("boolean"); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("IsBothFailed") + .HasColumnType("boolean"); + + b.Property("IsBothSuccess") + .HasColumnType("boolean"); + + b.Property("IsBuyerFailed") + .HasColumnType("boolean"); + + b.Property("IsBuyerSuccess") + .HasColumnType("boolean"); + + b.Property("IsSupplier") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.ToTable("AutoValidationRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.BulkUploadDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BulkUploadStatus") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DocUploadId") + .HasColumnType("text"); + + b.Property("FailedUserCount") + .HasColumnType("integer"); + + b.Property("FileKey") + .HasColumnType("text"); + + b.Property("FileKeyId") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MigrationEndedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("MigrationStartedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("MigrationStringContent") + .HasColumnType("text"); + + b.Property("OrganisationId") + .HasColumnType("text"); + + b.Property("ProcessedUserCount") + .HasColumnType("integer"); + + b.Property("TotalOrganisationCount") + .HasColumnType("integer"); + + b.Property("TotalUserCount") + .HasColumnType("integer"); + + b.Property("ValidationErrorDetails") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileKeyId") + .IsUnique(); + + b.ToTable("BulkUploadDetail"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ActivateOrganisations") + .HasColumnType("boolean"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("GlobalLevelOrganisationAccess") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("ServiceClientId") + .HasColumnType("text"); + + b.Property("ServiceCode") + .HasColumnType("text"); + + b.Property("ServiceName") + .HasColumnType("text"); + + b.Property("ServiceUrl") + .HasColumnType("text"); + + b.Property("TimeOutLength") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("CcsService"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceLogin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsServiceId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IdamUserLoginId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("TimedOut") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CcsServiceId"); + + b.HasIndex("IdamUserLoginId"); + + b.ToTable("CcsServiceLogin"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceRoleGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApprovalRequired") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DefaultEligibility") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MfaEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrgTypeEligibility") + .HasColumnType("integer"); + + b.Property("SubscriptionTypeEligibility") + .HasColumnType("integer"); + + b.Property("TradeEligibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("CcsServiceRoleGroup"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceRoleMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("CcsServiceRoleGroupId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.HasIndex("CcsServiceRoleGroupId"); + + b.ToTable("CcsServiceRoleMapping"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CountryDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Code") + .HasColumnType("text"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CountryDetails"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ExternalServiceRoleMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsServiceId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationEligibleRoleId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsServiceId"); + + b.HasIndex("OrganisationEligibleRoleId"); + + b.ToTable("ExternalServiceRoleMapping"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.IdamUserLogin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsLoginDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CcsLogoutDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ClientDevice") + .HasColumnType("text"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DeviceType") + .HasColumnType("integer"); + + b.Property("IdentityProviderId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("Location") + .HasColumnType("text"); + + b.Property("LoginSuccessful") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IdentityProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("IdamUserLogin"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.IdamUserLoginRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IdamUserLoginId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.HasIndex("IdamUserLoginId"); + + b.HasIndex("UserId"); + + b.ToTable("IdamUserLoginRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationAudit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Actioned") + .HasColumnType("text"); + + b.Property("ActionedBy") + .HasColumnType("text"); + + b.Property("ActionedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.Property("SchemeIdentifier") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationAudit"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationAuditEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Actioned") + .HasColumnType("text"); + + b.Property("ActionedBy") + .HasColumnType("text"); + + b.Property("Date") + .HasColumnType("timestamp without time zone"); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.Property("Roles") + .HasColumnType("text"); + + b.Property("SchemeIdentifier") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationAuditEvent"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleIdentityProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IdentityProviderId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IdentityProviderId"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationEligibleIdentityProvider"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MfaEnabled") + .HasColumnType("boolean"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationEligibleRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleRolePending", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationEligibleRolePending"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationGroupEligibleRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationEligibleRoleId") + .HasColumnType("integer"); + + b.Property("OrganisationUserGroupId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationEligibleRoleId"); + + b.HasIndex("OrganisationUserGroupId"); + + b.ToTable("OrganisationGroupEligibleRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ServicePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsServiceId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("ServicePermissionName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CcsServiceId"); + + b.ToTable("ServicePermission"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ServiceRolePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("ServicePermissionId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.HasIndex("ServicePermissionId"); + + b.ToTable("ServiceRolePermission"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.SiteContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AssignedContactType") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("ContactId") + .HasColumnType("integer"); + + b.Property("ContactPointId") + .HasColumnType("integer"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OriginalContactId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ContactPointId"); + + b.ToTable("SiteContact"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.UserIdentityProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationEligibleIdentityProviderId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationEligibleIdentityProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("UserIdentityProvider"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.CcsAccessRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApprovalRequired") + .HasColumnType("integer"); + + b.Property("CcsAccessRoleDescription") + .HasColumnType("text"); + + b.Property("CcsAccessRoleName") + .HasColumnType("text"); + + b.Property("CcsAccessRoleNameKey") + .HasColumnType("text"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DefaultEligibility") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MfaEnabled") + .HasColumnType("boolean"); + + b.Property("OrgTypeEligibility") + .HasColumnType("integer"); + + b.Property("SubscriptionTypeEligibility") + .HasColumnType("integer"); + + b.Property("TradeEligibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("CcsAccessRole"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp without time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp without time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ContactDetail"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AssignedContactType") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("ContactDetailId") + .HasColumnType("integer"); + + b.Property("ContactPointReasonId") + .HasColumnType("integer"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsSite") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OriginalContactPointId") + .HasColumnType("integer"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.Property("PartyTypeId") + .HasColumnType("integer"); + + b.Property("SiteName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ContactDetailId"); + + b.HasIndex("ContactPointReasonId"); + + b.HasIndex("PartyId"); + + b.HasIndex("PartyTypeId"); + + b.ToTable("ContactPoint"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactPointReason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ContactPointReason"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.EnterpriseType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("EnterpriseTypeName") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("EnterpriseType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.IdentityProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ExternalIdpFlag") + .HasColumnType("boolean"); + + b.Property("IdpConnectionName") + .HasColumnType("text"); + + b.Property("IdpName") + .HasColumnType("text"); + + b.Property("IdpUri") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("IdentityProvider"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Organisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BusinessType") + .HasColumnType("text"); + + b.Property("CcsServiceId") + .HasColumnType("integer"); + + b.Property("CiiOrganisationId") + .HasColumnType("text"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsSme") + .HasColumnType("boolean"); + + b.Property("IsVcse") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("LegalName") + .HasColumnType("text"); + + b.Property("OrganisationUri") + .HasColumnType("text"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.Property("RightToBuy") + .HasColumnType("boolean"); + + b.Property("SupplierBuyerType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsServiceId"); + + b.HasIndex("CiiOrganisationId"); + + b.HasIndex("PartyId") + .IsUnique(); + + b.ToTable("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationAccessRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationAccessRoleDescription") + .HasColumnType("text"); + + b.Property("OrganisationAccessRoleName") + .HasColumnType("text"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationAccessRole"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationEnterpriseType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("EnterpriseTypeId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("EnterpriseTypeId"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationEnterpriseType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationUserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MfaEnabled") + .HasColumnType("boolean"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.Property("UserGroupName") + .HasColumnType("text"); + + b.Property("UserGroupNameKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("OrganisationUserGroup"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Party", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("PartyTypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PartyTypeId"); + + b.ToTable("Party"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.PartyType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("PartyTypeName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PartyType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("PartyId") + .IsUnique(); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.PhysicalAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("ContactDetailId") + .HasColumnType("integer"); + + b.Property("CountryCode") + .HasColumnType("text"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("Locality") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("Region") + .HasColumnType("text"); + + b.Property("StreetAddress") + .HasColumnType("text"); + + b.Property("Uprn") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ContactDetailId") + .IsUnique(); + + b.ToTable("PhysicalAddress"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ProcurementGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("ProcurementGroup"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.RoleApprovalConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CcsAccessRoleId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("LinkExpiryDurationInMinute") + .HasColumnType("integer"); + + b.Property("NotificationEmails") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CcsAccessRoleId"); + + b.ToTable("RoleApprovalConfiguration"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.TradingOrganisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationId") + .HasColumnType("integer"); + + b.Property("TradingName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.ToTable("TradingOrganisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AccountVerified") + .HasColumnType("boolean"); + + b.Property("CcsServiceId") + .HasColumnType("integer"); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("DelegationAccepted") + .HasColumnType("boolean"); + + b.Property("DelegationEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DelegationStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("JobTitle") + .HasColumnType("text"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MfaEnabled") + .HasColumnType("boolean"); + + b.Property("OriginOrganizationId") + .HasColumnType("integer"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UserTitle") + .HasColumnType("integer"); + + b.Property("UserType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CcsServiceId"); + + b.HasIndex("OriginOrganizationId"); + + b.HasIndex("PartyId") + .IsUnique(); + + b.HasIndex("UserName"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserAccessRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationEligibleRoleId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationEligibleRoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserAccessRole"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserAccessRolePending", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("OrganisationEligibleRoleId") + .HasColumnType("integer"); + + b.Property("OrganisationUserGroupId") + .HasColumnType("integer"); + + b.Property("SendEmailNotification") + .HasColumnType("boolean"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationEligibleRoleId"); + + b.HasIndex("OrganisationUserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("UserAccessRolePending"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserGroupMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("MembershipEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("MembershipStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OrganisationUserGroupId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationUserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("UserGroupMembership"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserSettingTypeId") + .HasColumnType("integer"); + + b.Property("UserSettingValue") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserSettingTypeId"); + + b.ToTable("UserSetting"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserSettingType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("UserSettingName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserSettingType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.VirtualAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("ContactDetailId") + .HasColumnType("integer"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("VirtualAddressTypeId") + .HasColumnType("integer"); + + b.Property("VirtualAddressValue") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ContactDetailId"); + + b.HasIndex("VirtualAddressTypeId"); + + b.ToTable("VirtualAddress"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.VirtualAddressType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConcurrencyKey") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea"); + + b.Property("CreatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedUserId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastUpdatedOnUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdatedUserId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("VirtualAddressType"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.AutoValidationRole", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany() + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceLogin", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.CcsService", "CcsService") + .WithMany("CcsServiceLogins") + .HasForeignKey("CcsServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.Core.DbModel.Entity.IdamUserLogin", "IdamUserLogin") + .WithMany("CcsServiceLogins") + .HasForeignKey("IdamUserLoginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsService"); + + b.Navigation("IdamUserLogin"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceRoleMapping", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany() + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.Core.DbModel.Entity.CcsServiceRoleGroup", "CcsServiceRoleGroup") + .WithMany("CcsServiceRoleMappings") + .HasForeignKey("CcsServiceRoleGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + + b.Navigation("CcsServiceRoleGroup"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ExternalServiceRoleMapping", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.CcsService", "CcsService") + .WithMany("ExternalServiceRoleMappings") + .HasForeignKey("CcsServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", "OrganisationEligibleRole") + .WithMany("ExternalServiceRoleMappings") + .HasForeignKey("OrganisationEligibleRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsService"); + + b.Navigation("OrganisationEligibleRole"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.IdamUserLogin", b => + { + b.HasOne("CcsSso.DbModel.Entity.IdentityProvider", "IdentityProvider") + .WithMany("IdamUserLogins") + .HasForeignKey("IdentityProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("IdamUserLogins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityProvider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.IdamUserLoginRole", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany("IdamUserLoginRoles") + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.Core.DbModel.Entity.IdamUserLogin", null) + .WithMany("IdamUserLoginRoles") + .HasForeignKey("IdamUserLoginId"); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationAudit", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("OrganisationAudits") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationAuditEvent", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleIdentityProvider", b => + { + b.HasOne("CcsSso.DbModel.Entity.IdentityProvider", "IdentityProvider") + .WithMany() + .HasForeignKey("IdentityProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("OrganisationEligibleIdentityProviders") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityProvider"); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany("OrganisationEligibleRoles") + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("OrganisationEligibleRoles") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleRolePending", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany() + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationGroupEligibleRole", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", "OrganisationEligibleRole") + .WithMany() + .HasForeignKey("OrganisationEligibleRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.OrganisationUserGroup", "OrganisationUserGroup") + .WithMany("GroupEligibleRoles") + .HasForeignKey("OrganisationUserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganisationEligibleRole"); + + b.Navigation("OrganisationUserGroup"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ServicePermission", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.CcsService", "CcsService") + .WithMany("ServicePermissions") + .HasForeignKey("CcsServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsService"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ServiceRolePermission", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany("ServiceRolePermissions") + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.Core.DbModel.Entity.ServicePermission", "ServicePermission") + .WithMany("ServiceRolePermissions") + .HasForeignKey("ServicePermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + + b.Navigation("ServicePermission"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.SiteContact", b => + { + b.HasOne("CcsSso.DbModel.Entity.ContactPoint", "ContactPoint") + .WithMany("SiteContacts") + .HasForeignKey("ContactPointId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContactPoint"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.UserIdentityProvider", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.OrganisationEligibleIdentityProvider", "OrganisationEligibleIdentityProvider") + .WithMany("UserIdentityProviders") + .HasForeignKey("OrganisationEligibleIdentityProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("UserIdentityProviders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganisationEligibleIdentityProvider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactPoint", b => + { + b.HasOne("CcsSso.DbModel.Entity.ContactDetail", "ContactDetail") + .WithMany("ContactPoints") + .HasForeignKey("ContactDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.ContactPointReason", "ContactPointReason") + .WithMany("ContactPoints") + .HasForeignKey("ContactPointReasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Party", "Party") + .WithMany("ContactPoints") + .HasForeignKey("PartyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.PartyType", "PartyType") + .WithMany() + .HasForeignKey("PartyTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContactDetail"); + + b.Navigation("ContactPointReason"); + + b.Navigation("Party"); + + b.Navigation("PartyType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Organisation", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.CcsService", "CcsService") + .WithMany("CreatedOrganisations") + .HasForeignKey("CcsServiceId"); + + b.HasOne("CcsSso.DbModel.Entity.Party", "Party") + .WithOne("Organisation") + .HasForeignKey("CcsSso.DbModel.Entity.Organisation", "PartyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsService"); + + b.Navigation("Party"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationAccessRole", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("OrganisationAccessRoles") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationEnterpriseType", b => + { + b.HasOne("CcsSso.DbModel.Entity.EnterpriseType", "EnterpriseType") + .WithMany("OrganisationEnterpriseTypes") + .HasForeignKey("EnterpriseTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("OrganisationEnterpriseTypes") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EnterpriseType"); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationUserGroup", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("UserGroups") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Party", b => + { + b.HasOne("CcsSso.DbModel.Entity.PartyType", "PartyType") + .WithMany("Party") + .HasForeignKey("PartyTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartyType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Person", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("People") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.Party", "Party") + .WithOne("Person") + .HasForeignKey("CcsSso.DbModel.Entity.Person", "PartyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + + b.Navigation("Party"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.PhysicalAddress", b => + { + b.HasOne("CcsSso.DbModel.Entity.ContactDetail", "ContactDetail") + .WithOne("PhysicalAddress") + .HasForeignKey("CcsSso.DbModel.Entity.PhysicalAddress", "ContactDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContactDetail"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ProcurementGroup", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.RoleApprovalConfiguration", b => + { + b.HasOne("CcsSso.DbModel.Entity.CcsAccessRole", "CcsAccessRole") + .WithMany() + .HasForeignKey("CcsAccessRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsAccessRole"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.TradingOrganisation", b => + { + b.HasOne("CcsSso.DbModel.Entity.Organisation", "Organisation") + .WithMany("TradingOrganisations") + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organisation"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.User", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.CcsService", "CcsService") + .WithMany("CreatedUsers") + .HasForeignKey("CcsServiceId"); + + b.HasOne("CcsSso.DbModel.Entity.Organisation", "OriginOrganization") + .WithMany() + .HasForeignKey("OriginOrganizationId"); + + b.HasOne("CcsSso.DbModel.Entity.Party", "Party") + .WithOne("User") + .HasForeignKey("CcsSso.DbModel.Entity.User", "PartyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CcsService"); + + b.Navigation("OriginOrganization"); + + b.Navigation("Party"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserAccessRole", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", "OrganisationEligibleRole") + .WithMany() + .HasForeignKey("OrganisationEligibleRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("UserAccessRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganisationEligibleRole"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserAccessRolePending", b => + { + b.HasOne("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", "OrganisationEligibleRole") + .WithMany() + .HasForeignKey("OrganisationEligibleRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.OrganisationUserGroup", "OrganisationUserGroup") + .WithMany() + .HasForeignKey("OrganisationUserGroupId"); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("UserAccessRolePending") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganisationEligibleRole"); + + b.Navigation("OrganisationUserGroup"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserGroupMembership", b => + { + b.HasOne("CcsSso.DbModel.Entity.OrganisationUserGroup", "OrganisationUserGroup") + .WithMany("UserGroupMemberships") + .HasForeignKey("OrganisationUserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("UserGroupMemberships") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganisationUserGroup"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserSetting", b => + { + b.HasOne("CcsSso.DbModel.Entity.User", "User") + .WithMany("UserSettings") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.UserSettingType", "UserSettingType") + .WithMany("UserSettings") + .HasForeignKey("UserSettingTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("UserSettingType"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.VirtualAddress", b => + { + b.HasOne("CcsSso.DbModel.Entity.ContactDetail", "ContactDetail") + .WithMany("VirtualAddresses") + .HasForeignKey("ContactDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CcsSso.DbModel.Entity.VirtualAddressType", "VirtualAddressType") + .WithMany("VirtualAddresses") + .HasForeignKey("VirtualAddressTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContactDetail"); + + b.Navigation("VirtualAddressType"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsService", b => + { + b.Navigation("CcsServiceLogins"); + + b.Navigation("CreatedOrganisations"); + + b.Navigation("CreatedUsers"); + + b.Navigation("ExternalServiceRoleMappings"); + + b.Navigation("ServicePermissions"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.CcsServiceRoleGroup", b => + { + b.Navigation("CcsServiceRoleMappings"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.IdamUserLogin", b => + { + b.Navigation("CcsServiceLogins"); + + b.Navigation("IdamUserLoginRoles"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleIdentityProvider", b => + { + b.Navigation("UserIdentityProviders"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.OrganisationEligibleRole", b => + { + b.Navigation("ExternalServiceRoleMappings"); + }); + + modelBuilder.Entity("CcsSso.Core.DbModel.Entity.ServicePermission", b => + { + b.Navigation("ServiceRolePermissions"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.CcsAccessRole", b => + { + b.Navigation("IdamUserLoginRoles"); + + b.Navigation("OrganisationEligibleRoles"); + + b.Navigation("ServiceRolePermissions"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactDetail", b => + { + b.Navigation("ContactPoints"); + + b.Navigation("PhysicalAddress"); + + b.Navigation("VirtualAddresses"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactPoint", b => + { + b.Navigation("SiteContacts"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.ContactPointReason", b => + { + b.Navigation("ContactPoints"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.EnterpriseType", b => + { + b.Navigation("OrganisationEnterpriseTypes"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.IdentityProvider", b => + { + b.Navigation("IdamUserLogins"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Organisation", b => + { + b.Navigation("OrganisationAccessRoles"); + + b.Navigation("OrganisationAudits"); + + b.Navigation("OrganisationEligibleIdentityProviders"); + + b.Navigation("OrganisationEligibleRoles"); + + b.Navigation("OrganisationEnterpriseTypes"); + + b.Navigation("People"); + + b.Navigation("TradingOrganisations"); + + b.Navigation("UserGroups"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.OrganisationUserGroup", b => + { + b.Navigation("GroupEligibleRoles"); + + b.Navigation("UserGroupMemberships"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.Party", b => + { + b.Navigation("ContactPoints"); + + b.Navigation("Organisation"); + + b.Navigation("Person"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.PartyType", b => + { + b.Navigation("Party"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.User", b => + { + b.Navigation("IdamUserLogins"); + + b.Navigation("UserAccessRolePending"); + + b.Navigation("UserAccessRoles"); + + b.Navigation("UserGroupMemberships"); + + b.Navigation("UserIdentityProviders"); + + b.Navigation("UserSettings"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.UserSettingType", b => + { + b.Navigation("UserSettings"); + }); + + modelBuilder.Entity("CcsSso.DbModel.Entity.VirtualAddressType", b => + { + b.Navigation("VirtualAddresses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.cs b/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.cs new file mode 100644 index 00000000..91c25c68 --- /dev/null +++ b/api/CcsSso.Core.DbMigrations/Migrations/20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace CcsSso.Core.DbMigrations.Migrations +{ + public partial class Add_UserAccessRolePending_OrganisationUserGroupId : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrganisationUserGroupId", + table: "UserAccessRolePending", + type: "integer", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_UserAccessRolePending_OrganisationUserGroupId", + table: "UserAccessRolePending", + column: "OrganisationUserGroupId"); + + migrationBuilder.AddForeignKey( + name: "FK_UserAccessRolePending_OrganisationUserGroup_OrganisationUse~", + table: "UserAccessRolePending", + column: "OrganisationUserGroupId", + principalTable: "OrganisationUserGroup", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_UserAccessRolePending_OrganisationUserGroup_OrganisationUse~", + table: "UserAccessRolePending"); + + migrationBuilder.DropIndex( + name: "IX_UserAccessRolePending_OrganisationUserGroupId", + table: "UserAccessRolePending"); + + migrationBuilder.DropColumn( + name: "OrganisationUserGroupId", + table: "UserAccessRolePending"); + } + } +} diff --git a/api/CcsSso.Core.DbMigrations/Migrations/DataContextModelSnapshot.cs b/api/CcsSso.Core.DbMigrations/Migrations/DataContextModelSnapshot.cs index d6081ae2..387d52d0 100644 --- a/api/CcsSso.Core.DbMigrations/Migrations/DataContextModelSnapshot.cs +++ b/api/CcsSso.Core.DbMigrations/Migrations/DataContextModelSnapshot.cs @@ -1889,6 +1889,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganisationEligibleRoleId") .HasColumnType("integer"); + b.Property("OrganisationUserGroupId") + .HasColumnType("integer"); + b.Property("SendEmailNotification") .HasColumnType("boolean"); @@ -1902,6 +1905,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrganisationEligibleRoleId"); + b.HasIndex("OrganisationUserGroupId"); + b.HasIndex("UserId"); b.ToTable("UserAccessRolePending"); @@ -2603,6 +2608,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("CcsSso.DbModel.Entity.OrganisationUserGroup", "OrganisationUserGroup") + .WithMany() + .HasForeignKey("OrganisationUserGroupId"); + b.HasOne("CcsSso.DbModel.Entity.User", "User") .WithMany("UserAccessRolePending") .HasForeignKey("UserId") @@ -2611,6 +2620,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("OrganisationEligibleRole"); + b.Navigation("OrganisationUserGroup"); + b.Navigation("User"); }); diff --git a/api/CcsSso.Core.DbMigrations/Scripts/Phase3_Sprint8/1_Add_UserAccessRolePending_OrganisationUserGroupId.sql b/api/CcsSso.Core.DbMigrations/Scripts/Phase3_Sprint8/1_Add_UserAccessRolePending_OrganisationUserGroupId.sql new file mode 100644 index 00000000..049fab63 --- /dev/null +++ b/api/CcsSso.Core.DbMigrations/Scripts/Phase3_Sprint8/1_Add_UserAccessRolePending_OrganisationUserGroupId.sql @@ -0,0 +1,13 @@ +START TRANSACTION; + +ALTER TABLE "UserAccessRolePending" ADD "OrganisationUserGroupId" integer NULL; + +CREATE INDEX "IX_UserAccessRolePending_OrganisationUserGroupId" ON "UserAccessRolePending" ("OrganisationUserGroupId"); + +ALTER TABLE "UserAccessRolePending" ADD CONSTRAINT "FK_UserAccessRolePending_OrganisationUserGroup_OrganisationUse~" FOREIGN KEY ("OrganisationUserGroupId") REFERENCES "OrganisationUserGroup" ("Id") ON DELETE RESTRICT; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20230327094219_Add_UserAccessRolePending_OrganisationUserGroupId', '5.0.10'); + +COMMIT; + diff --git a/api/CcsSso.Core.DbModel/Entity/UserAccessRolePending.cs b/api/CcsSso.Core.DbModel/Entity/UserAccessRolePending.cs index 45b7356b..0d115b60 100644 --- a/api/CcsSso.Core.DbModel/Entity/UserAccessRolePending.cs +++ b/api/CcsSso.Core.DbModel/Entity/UserAccessRolePending.cs @@ -21,5 +21,9 @@ public class UserAccessRolePending : BaseEntity public int Status { get; set; } public bool SendEmailNotification { get; set; } = true; + + public OrganisationUserGroup OrganisationUserGroup { get; set; } + + public int? OrganisationUserGroupId { get; set; } } } diff --git a/api/CcsSso.Core.Domain/Constants/Constants.cs b/api/CcsSso.Core.Domain/Constants/Constants.cs index 9a7129a9..a7c53fd7 100644 --- a/api/CcsSso.Core.Domain/Constants/Constants.cs +++ b/api/CcsSso.Core.Domain/Constants/Constants.cs @@ -83,6 +83,8 @@ public static class ErrorConstant public const string ErrorLinkExpired = "ERROR_LINK_EXPIRED"; public const string ErrorUserAlreadyExists = "ERROR_USER_ALREADY_EXISTS"; + public const string ErrorUserRoleAlreadyExists = "ERROR_USER_ROLE_ALREADY_EXISTS"; + } public static class VirtualContactTypeName diff --git a/api/CcsSso.Core.Domain/Contracts/External/IOrganisationGroupService.cs b/api/CcsSso.Core.Domain/Contracts/External/IOrganisationGroupService.cs index a4f7ed35..87023c0a 100644 --- a/api/CcsSso.Core.Domain/Contracts/External/IOrganisationGroupService.cs +++ b/api/CcsSso.Core.Domain/Contracts/External/IOrganisationGroupService.cs @@ -21,6 +21,9 @@ public interface IOrganisationGroupService Task GetServiceRoleGroupAsync(string ciiOrganisationId, int groupId); + Task GetGroupUsersPendingRequestSummary(int groupId, string ciiOrgId, ResultSetCriteria resultSetCriteria, bool isPendingApproval); + + Task UpdateServiceRoleGroupAsync(string ciiOrganisationId, int groupId, OrganisationServiceRoleGroupRequestInfo organisationServiceRoleGroupRequestInfo); } } diff --git a/api/CcsSso.Core.Domain/Contracts/External/IUserProfileRoleApprovalService.cs b/api/CcsSso.Core.Domain/Contracts/External/IUserProfileRoleApprovalService.cs index 10e6f231..0680372e 100644 --- a/api/CcsSso.Core.Domain/Contracts/External/IUserProfileRoleApprovalService.cs +++ b/api/CcsSso.Core.Domain/Contracts/External/IUserProfileRoleApprovalService.cs @@ -12,7 +12,7 @@ public interface IUserProfileRoleApprovalService Task VerifyAndReturnRoleApprovalTokenDetailsAsync(string token); - Task RemoveApprovalPendingRolesAsync(string userName, string roles); + Task RemoveApprovalPendingRolesAsync(string userName, string roles, int? groupId = null); Task CreateUserRolesPendingForApprovalAsync(UserProfileEditRequestInfo userProfileRequestInfo, bool sendEmailNotification = true); diff --git a/api/CcsSso.Core.Domain/Dtos/External/OrganisationGroupInfo.cs b/api/CcsSso.Core.Domain/Dtos/External/OrganisationGroupInfo.cs index 5f24220c..e653b5a0 100644 --- a/api/CcsSso.Core.Domain/Dtos/External/OrganisationGroupInfo.cs +++ b/api/CcsSso.Core.Domain/Dtos/External/OrganisationGroupInfo.cs @@ -1,3 +1,4 @@ +using CcsSso.Core.DbModel.Constants; using System; using System.Collections.Generic; @@ -74,6 +75,14 @@ public class GroupServiceRoleGroup public string Description { get; set; } } + public class GroupUserListResponse:PaginationInfo + { + public int groupId { get; set; } + + public List GroupUser { get; set; } + + } + public class GroupUser { public string UserId { get; set; } @@ -81,6 +90,9 @@ public class GroupUser public string Name { get; set; } public bool IsAdmin { get; set; } = false; + + public UserPendingRoleStaus? UserPendingRoleStatus { get; set; } + } public class OrganisationGroupRolePatchInfo diff --git a/api/CcsSso.Core.Domain/Dtos/External/UserProfileResponseInfo.cs b/api/CcsSso.Core.Domain/Dtos/External/UserProfileResponseInfo.cs index 75841fa8..d44f00ca 100644 --- a/api/CcsSso.Core.Domain/Dtos/External/UserProfileResponseInfo.cs +++ b/api/CcsSso.Core.Domain/Dtos/External/UserProfileResponseInfo.cs @@ -44,6 +44,8 @@ public class UserRequestMain public class UserRequestDetail : UserRequestMain { + public int? GroupId { get; set; } + public List RoleIds { get; set; } } diff --git a/api/CcsSso.Core.ExternalApi/Controllers/OrganisationProfileController.cs b/api/CcsSso.Core.ExternalApi/Controllers/OrganisationProfileController.cs index 92e00ad5..e330acf8 100644 --- a/api/CcsSso.Core.ExternalApi/Controllers/OrganisationProfileController.cs +++ b/api/CcsSso.Core.ExternalApi/Controllers/OrganisationProfileController.cs @@ -1100,6 +1100,40 @@ public async Task UpdateOrganisationServiceRoleGroup(string organisationId, int { await _organisationGroupService.UpdateServiceRoleGroupAsync(organisationId, groupId, organisationServiceRoleGroupRequestInfo); } + + /// + /// Get organisation group users and their role approval status + /// + /// Ok + /// Unauthorised + /// Forbidden + /// Resource not found + /// + /// Sample request: + /// + /// GET /organisations/1/groups/1/groupusers + /// + /// + [HttpGet("{organisationId}/groups/{groupId}/groupusers")] + [ClaimAuthorise("ORG_ADMINISTRATOR")] + [OrganisationAuthorise("ORGANISATION")] + [SwaggerOperation(Tags = new[] { "Organisation Group" })] + [ProducesResponseType(typeof(GroupUserListResponse), 200)] + public async Task GetGroupUsersPendingRequestSummary(string organisationId, int groupId, + [FromQuery] ResultSetCriteria resultSetCriteria, + [FromQuery(Name = "is-pending-approval")] bool isPendingApproval = false) + { + resultSetCriteria ??= new ResultSetCriteria + { + CurrentPage = 1, + PageSize = 10 + }; + resultSetCriteria.CurrentPage = resultSetCriteria.CurrentPage <= 0 ? 1 : resultSetCriteria.CurrentPage; + resultSetCriteria.PageSize = resultSetCriteria.PageSize <= 0 ? 10 : resultSetCriteria.PageSize; + + return await _organisationGroupService.GetGroupUsersPendingRequestSummary(groupId, organisationId, resultSetCriteria,isPendingApproval); + } + #endregion #region Organisation IdentityProviders @@ -1431,7 +1465,7 @@ public async Task AutoValidateOrganisationTypeswitch(string organisationId, Orga [HttpPost("{ciiOrganisationId}/autovalidationjob")] [SwaggerOperation(Tags = new[] { "AutoValidation" })] [ProducesResponseType(typeof(string), 200)] - public async Task> AutovalidationJob(string ciiOrganisationId) + public async Task> AutovalidationJob(string ciiOrganisationId) { return await _organisationService.AutoValidateOrganisationJob(ciiOrganisationId); } diff --git a/api/CcsSso.Core.ExternalApi/Controllers/UserRolesApprovalController.cs b/api/CcsSso.Core.ExternalApi/Controllers/UserRolesApprovalController.cs index e9b90063..d0449711 100644 --- a/api/CcsSso.Core.ExternalApi/Controllers/UserRolesApprovalController.cs +++ b/api/CcsSso.Core.ExternalApi/Controllers/UserRolesApprovalController.cs @@ -63,7 +63,7 @@ public async Task> GetUserRolesPendingForAppr /// /// Sample request: /// - /// DELETE /approve/role?user-id=user@mail.com&roles=1,2 + /// DELETE /approve/role?user-id=user@mail.com&roles=1,2&groupId=1 /// /// /// @@ -72,9 +72,9 @@ public async Task> GetUserRolesPendingForAppr [OrganisationAuthorise("USER")] [SwaggerOperation(Tags = new[] { "User" })] [ProducesResponseType(typeof(void), 200)] - public async Task RemoveApprovalPendingRoles([FromQuery(Name = "user-id")] string userId, [FromQuery(Name = "roles")] string roleIds) + public async Task RemoveApprovalPendingRoles([FromQuery(Name = "user-id")] string userId, [FromQuery(Name = "roles")] string roleIds, [FromQuery(Name = "groupId")] int? groupId) { - await _userProfileRoleApprovalService.RemoveApprovalPendingRolesAsync(userId, roleIds); + await _userProfileRoleApprovalService.RemoveApprovalPendingRolesAsync(userId, roleIds, groupId); } /// @@ -115,7 +115,8 @@ public async Task VerifyRoleApprovalToken([Fr /// { /// "userName": "user@mail.com", /// "detail": { - /// "roleIds": { 1, 2 } + /// "roleIds": { 1, 2 }, + /// "groupId": null /// } /// } /// diff --git a/api/CcsSso.Core.JobScheduler/Jobs/RoleApprovalLinkExpiredJob.cs b/api/CcsSso.Core.JobScheduler/Jobs/RoleApprovalLinkExpiredJob.cs index 405638c2..2bdfba66 100644 --- a/api/CcsSso.Core.JobScheduler/Jobs/RoleApprovalLinkExpiredJob.cs +++ b/api/CcsSso.Core.JobScheduler/Jobs/RoleApprovalLinkExpiredJob.cs @@ -83,6 +83,7 @@ public async Task> GetPendingRoleApproval() var userAccessRolePendingAllList = await _dataContext.UserAccessRolePending .Include(u => u.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) .Where(u => !u.IsDeleted && u.Status == (int)UserPendingRoleStaus.Pending) + .OrderBy(u => u.CreatedOnUtc) .ToListAsync(); return userAccessRolePendingAllList; diff --git a/api/CcsSso.Core.JobScheduler/Services/RoleApprovalLinkExpiredService.cs b/api/CcsSso.Core.JobScheduler/Services/RoleApprovalLinkExpiredService.cs index 0d9d2106..8fa5715a 100644 --- a/api/CcsSso.Core.JobScheduler/Services/RoleApprovalLinkExpiredService.cs +++ b/api/CcsSso.Core.JobScheduler/Services/RoleApprovalLinkExpiredService.cs @@ -41,22 +41,52 @@ public RoleApprovalLinkExpiredService(IServiceScopeFactory factory, public async Task PerformJobAsync(List pendingRoles) { - var approvalRoleConfig = await _dataContext.RoleApprovalConfiguration.Where(x => !x.IsDeleted).ToListAsync(); List expiredUserAccessRolePendingList = new(); + List relatedExpiredUserAccessRolePendingList = new(); foreach (var role in pendingRoles) { - var roleExpireTime = role.LastUpdatedOnUtc.AddMinutes(approvalRoleConfig.FirstOrDefault(x => x.CcsAccessRoleId == - role.OrganisationEligibleRole.CcsAccessRole.Id).LinkExpiryDurationInMinute); + var roleConfig = approvalRoleConfig.FirstOrDefault(x => x.CcsAccessRoleId == + role.OrganisationEligibleRole.CcsAccessRole.Id); + + if(roleConfig == null) + continue; + + var roleExpireTime = role.LastUpdatedOnUtc.AddMinutes(roleConfig.LinkExpiryDurationInMinute); if (roleExpireTime < DateTime.UtcNow) { - expiredUserAccessRolePendingList.Add(role); + var isExistInRelatedList = relatedExpiredUserAccessRolePendingList.Any(x => x.Id == role.Id); + + if (!isExistInRelatedList) + { + expiredUserAccessRolePendingList.Add(role); + + var relatedPendingRoles = pendingRoles.Where(x => x.Id != role.Id && x.UserId == role.UserId).ToList(); + relatedExpiredUserAccessRolePendingList.AddRange(relatedPendingRoles); + } } } + await ProcessRelatedExpiredUserAccessRolePending(relatedExpiredUserAccessRolePendingList); + await ProcessExpiredUserAccessRolePending(expiredUserAccessRolePendingList); + } + + private async Task ProcessRelatedExpiredUserAccessRolePending(List relatedExpiredUserAccessRolePendingList) + { + _logger.LogInformation($"Total number of related expired Roles: {relatedExpiredUserAccessRolePendingList.Count()}"); + + if (relatedExpiredUserAccessRolePendingList.Any()) + { + await RemoveExpiredApprovalPendingRolesAsync(relatedExpiredUserAccessRolePendingList); + _logger.LogInformation($"Successfully updated the related expired roles"); + } + } + + private async Task ProcessExpiredUserAccessRolePending(List expiredUserAccessRolePendingList) + { _logger.LogInformation($"Total number of expired Roles: {expiredUserAccessRolePendingList.Count()}"); if (expiredUserAccessRolePendingList.Any()) @@ -67,7 +97,6 @@ public async Task PerformJobAsync(List pendingRoles) _logger.LogInformation($"Sending email if it is eligible"); await SendEmail(expiredUserAccessRolePendingList); _logger.LogInformation($"Finished sending email"); - } } @@ -78,9 +107,7 @@ private async Task RemoveExpiredApprovalPendingRolesAsync(List { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Expired; }); - await _dataContext.SaveChangesAsync(); - - + await _dataContext.SaveChangesAsync(); } } diff --git a/api/CcsSso.Core.PPONScheduler/CcsSso.Core.PPONScheduler.csproj b/api/CcsSso.Core.PPONScheduler/CcsSso.Core.PPONScheduler.csproj new file mode 100644 index 00000000..9a333ea9 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/CcsSso.Core.PPONScheduler.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/api/CcsSso.Core.PPONScheduler/Jobs/OneTimePPONJob.cs b/api/CcsSso.Core.PPONScheduler/Jobs/OneTimePPONJob.cs new file mode 100644 index 00000000..c8469fcb --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Jobs/OneTimePPONJob.cs @@ -0,0 +1,109 @@ +using CcsSso.Core.PPONScheduler.Model; +using CcsSso.Core.PPONScheduler.Service.Contracts; +using CcsSso.Domain.Contracts; +using CcsSso.Shared.Contracts; +using System.Globalization; + +namespace CcsSso.Core.PPONScheduler.Jobs +{ + public class OneTimePPONJob : BackgroundService + { + private readonly PPONAppSettings _appSettings; + private readonly IDataContext _dataContext; + private readonly IPPONService _pPONService; + private readonly ILogger _logger; + private bool ranOnce; + private DateTime startDate; + private DateTime endDate; + + + public OneTimePPONJob(ILogger logger, PPONAppSettings appSettings, + IServiceScopeFactory factory) + { + _logger = logger; + _appSettings = appSettings; + ranOnce = false; + _pPONService = factory.CreateScope().ServiceProvider.GetRequiredService(); + _dataContext = factory.CreateScope().ServiceProvider.GetRequiredService(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + int interval = _appSettings.ScheduleJobSettings.ScheduleInMinutes * 60000; + var oneTimeValidationSwitch = _appSettings.OneTimeJobSettings.Switch; + if (oneTimeValidationSwitch && ranOnce) + { + _logger.LogInformation("One time validation ran already. Skipping this iteration."); + } + else if (oneTimeValidationSwitch) + { + bool isDateValid = ValidateDate(); + if (isDateValid) + { + await PerformJob(oneTimeValidationSwitch); + } + } + await Task.Delay(interval, stoppingToken); + } + } + + private bool ValidateDate() + { + var isDateValid = true; + var startDateString = _appSettings.OneTimeJobSettings.StartDate; + var endDateString = _appSettings.OneTimeJobSettings.EndDate; + + if (startDateString == null || endDateString == null) + { + _logger.LogError("One time validation needs start and end date. Skipping this iteration."); + isDateValid = false; + } + + if (isDateValid) + { + isDateValid = ConvertDate(startDateString, endDateString); + } + + return isDateValid; + } + + private bool ConvertDate(string? startDateString, string? endDateString) + { + var isDateValid = true; + + try + { + startDate = DateTime.ParseExact(startDateString, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture); + endDate = DateTime.ParseExact(endDateString, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture); + } + catch (FormatException) + { + _logger.LogError("{0} or {1} is not in the correct format. Date format should be as follows 'yyyy-MM-dd HH:mm' Skipping this iteration.", startDateString, endDateString); + isDateValid = false; + } + catch (Exception) + { + _logger.LogError("Error while reading the start or end date {0}, {1}. Skipping this iteration.", startDateString, endDateString); + isDateValid = false; + } + + return isDateValid; + } + + private async Task PerformJob(bool oneTimeValidationSwitch) + { + _logger.LogInformation(""); + _logger.LogInformation("One time validation job switched on. So it runs once to process all the organisation between dates"); + + _logger.LogInformation("PPON one time job started at: {time}", DateTimeOffset.Now); + + await _pPONService.PerformJob(oneTimeValidationSwitch, startDate, endDate); + ranOnce = true; + + _logger.LogInformation("PPON one time job Finsied at: {time}", DateTimeOffset.Now); + _logger.LogInformation(""); + } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Jobs/PPONJob.cs b/api/CcsSso.Core.PPONScheduler/Jobs/PPONJob.cs new file mode 100644 index 00000000..b6dc88f8 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Jobs/PPONJob.cs @@ -0,0 +1,51 @@ +using CcsSso.Core.PPONScheduler.Model; +using CcsSso.Core.PPONScheduler.Service; +using CcsSso.Core.PPONScheduler.Service.Contracts; +using CcsSso.Domain.Contracts; +using CcsSso.Shared.Contracts; + +namespace CcsSso.Core.PPONScheduler.Jobs +{ + public class PPONJob : BackgroundService + { + private readonly PPONAppSettings _appSettings; + private readonly IDataContext _dataContext; + private readonly IPPONService _pPONService; + private readonly ILogger _logger; + private DateTime startDate; + private DateTime endDate; + + + public PPONJob(ILogger logger, PPONAppSettings appSettings, + IServiceScopeFactory factory) + { + _logger = logger; + _appSettings = appSettings; + _pPONService = factory.CreateScope().ServiceProvider.GetRequiredService(); + _dataContext = factory.CreateScope().ServiceProvider.GetRequiredService(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + int interval = _appSettings.ScheduleJobSettings.ScheduleInMinutes * 60000; + + var oneTimeValidationSwitch = _appSettings.OneTimeJobSettings.Switch; + + _logger.LogInformation(""); + _logger.LogInformation("PPON Scheduled job started at: {time}", DateTimeOffset.Now); + + if (!oneTimeValidationSwitch) + { + await _pPONService.PerformJob(oneTimeValidationSwitch, startDate, endDate); + } + + _logger.LogInformation("PPON Scheduled job Finsied at: {time}", DateTimeOffset.Now); + _logger.LogInformation(""); + + await Task.Delay(interval, stoppingToken); + } + } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Model/Organisation.cs b/api/CcsSso.Core.PPONScheduler/Model/Organisation.cs new file mode 100644 index 00000000..6f27bafd --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Model/Organisation.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CcsSso.Core.PPONScheduler.Model +{ + public class Organisation + { + public int Id { get; set; } + + public string CiiOrganisationId { get; set; } + + public string LegalName { get; set; } + + public bool? RightToBuy { get; set; } + + public DateTime CreatedOnUtc { get; set; } + + public int? SupplierBuyerType { get; set; } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Model/PPONAppSettings.cs b/api/CcsSso.Core.PPONScheduler/Model/PPONAppSettings.cs new file mode 100644 index 00000000..4804f688 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Model/PPONAppSettings.cs @@ -0,0 +1,51 @@ +using CcsSso.Core.Domain.Jobs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CcsSso.Core.PPONScheduler.Model +{ + public class PPONAppSettings + { + public string DbConnection { get; set; } + + public ApiSettings? PPONApiSettings { get; set; } + + public CiiSettings? CiiSettings { get; set; } + + public ScheduleJob? ScheduleJobSettings { get; set; } + + public OneTimeJob? OneTimeJobSettings { get; set; } + + } + public class ApiSettings + { + public string? Key { get; set; } + + public string? Url { get; set; } + } + + public class CiiSettings + { + public string? Url { get; set; } + + public string? Token { get; set; } + } + + public class ScheduleJob + { + public int ScheduleInMinutes { get; set; } + public int DataDurationInMinutes { get; set; } + } + + public class OneTimeJob + { + public bool Switch { get; set; } + + public string StartDate { get; set; } + + public string EndDate { get; set; } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Model/PPONDetails.cs b/api/CcsSso.Core.PPONScheduler/Model/PPONDetails.cs new file mode 100644 index 00000000..4f679c92 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Model/PPONDetails.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CcsSso.Core.PPONScheduler.Model +{ + public class Identifier + { + public string Id { get; set; } + + [JsonProperty("id-type")] + public string IdType { get; set; } + + public bool Persisted { get; set; } + } + + public class PPONDetails + { + public List Identifiers { get; set; } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Program.cs b/api/CcsSso.Core.PPONScheduler/Program.cs new file mode 100644 index 00000000..f047c3a1 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Program.cs @@ -0,0 +1,140 @@ +using CcsSso.Core.PPONScheduler.Jobs; +using CcsSso.Core.PPONScheduler.Model; +using CcsSso.DbPersistence; +using CcsSso.Domain.Contracts; +using CcsSso.Shared.Contracts; +using CcsSso.Shared.Services; +using Microsoft.EntityFrameworkCore; +using CcsSso.Shared.Domain.Contexts; +using CcsSso.Core.PPONScheduler.Service.Contracts; +using CcsSso.Core.PPONScheduler.Service; +using CcsSso.Service; +using CcsSso.Core.Service; +using CcsSso.Core.Domain.Contracts; + +namespace CcsSso.Core.PPONScheduler +{ + public class Program + { + private static bool vaultEnabled; + + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, config) => + { + PopulateAppConfiguration(config); + }) + + .ConfigureServices((hostContext, services) => + { + PPONAppSettings appSettings = GetConfigurationDetails(hostContext); + ConfigureHttpClients(services, appSettings); + ConfigureModels(services, appSettings); + ConfigureServices(services, appSettings); + ConfigureContexts(services, appSettings); + ConfigureJobs(services); + }); + } + + private static void PopulateAppConfiguration(IConfigurationBuilder config) + { + var configBuilder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: false) + .Build(); + + var builtConfig = config.Build(); + vaultEnabled = configBuilder.GetValue("VaultEnabled"); + + if (!vaultEnabled) + { + config.AddJsonFile("appsecrets.json", optional: false, reloadOnChange: true); + } + } + + private static void ConfigureModels(IServiceCollection services, PPONAppSettings appSettings) + { + services.AddSingleton(s => + { + Dtos.Domain.Models.CiiConfig ciiConfigInfo = new Dtos.Domain.Models.CiiConfig() + { + url = appSettings.CiiSettings.Url, + token = appSettings.CiiSettings.Token + }; + return ciiConfigInfo; + }); + } + + private static void ConfigureJobs(IServiceCollection services) + { + services.AddHostedService(); + services.AddHostedService(); + } + + private static void ConfigureContexts(IServiceCollection services, PPONAppSettings appSettings) + { + services.AddScoped(s => new RequestContext { UserId = -1 }); // Set context user id to -1 to identify the updates done by the job + services.AddDbContext(options => options.UseNpgsql(appSettings.DbConnection)); + } + + private static void ConfigureServices(IServiceCollection services, PPONAppSettings appSettings) + { + services.AddSingleton(s => appSettings); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + } + + private static void ConfigureHttpClients(IServiceCollection services, PPONAppSettings appSettings) + { + services.AddHttpClient("PPONApi", c => + { + c.BaseAddress = new Uri(appSettings.PPONApiSettings.Url); + c.DefaultRequestHeaders.Add("x-api-key", appSettings.PPONApiSettings.Key); + }); + services.AddHttpClient("CiiApi", c => + { + c.BaseAddress = new Uri(appSettings.CiiSettings.Url); + c.DefaultRequestHeaders.Add("x-api-key", appSettings.CiiSettings.Token); + }); + } + + private static PPONAppSettings GetConfigurationDetails(HostBuilderContext hostContext) + { + ScheduleJob scheduleJob; + OneTimeJob oneTimeJob; + CiiSettings ciiSettings; + ApiSettings pPONApiSettings; + + string dbConnection; + + var config = hostContext.Configuration; + dbConnection = config["DbConnection"]; + + ciiSettings = config.GetSection("CIIApi").Get(); + pPONApiSettings = config.GetSection("PPONApi").Get(); + + scheduleJob = config.GetSection("ScheduleJob").Get(); + oneTimeJob = config.GetSection("OneTimeJob").Get(); + + var appSettings = new PPONAppSettings() + { + DbConnection = dbConnection, + CiiSettings = ciiSettings, + PPONApiSettings = pPONApiSettings, + ScheduleJobSettings = scheduleJob, + OneTimeJobSettings = oneTimeJob, + }; + + return appSettings; + } + } +} + diff --git a/api/CcsSso.Core.PPONScheduler/Properties/launchSettings.json b/api/CcsSso.Core.PPONScheduler/Properties/launchSettings.json new file mode 100644 index 00000000..78ab093e --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "CcsSso.Core.ServiceOnboardingScheduler": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Service/Contracts/IPPONService.cs b/api/CcsSso.Core.PPONScheduler/Service/Contracts/IPPONService.cs new file mode 100644 index 00000000..b9336cd4 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Service/Contracts/IPPONService.cs @@ -0,0 +1,14 @@ +using CcsSso.Core.PPONScheduler.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CcsSso.Core.PPONScheduler.Service.Contracts +{ + public interface IPPONService + { + Task PerformJob(bool oneTimeValidationSwitch, DateTime startDate, DateTime endDate); + } +} diff --git a/api/CcsSso.Core.PPONScheduler/Service/PPONService.cs b/api/CcsSso.Core.PPONScheduler/Service/PPONService.cs new file mode 100644 index 00000000..ba5ea96b --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/Service/PPONService.cs @@ -0,0 +1,215 @@ +using CcsSso.Core.PPONScheduler.Model; +using CcsSso.Core.PPONScheduler.Service.Contracts; +using CcsSso.Domain.Constants; +using CcsSso.Domain.Contracts; +using CcsSso.Dtos.Domain.Models; +using CcsSso.Shared.Contracts; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; + +namespace CcsSso.Core.PPONScheduler.Service +{ + public class PPONService : IPPONService + { + private readonly IDataContext _dataContext; + private readonly IHttpClientFactory _httpClientFactory; + private readonly PPONAppSettings _appSettings; + private readonly IDateTimeService _dataTimeService; + private readonly ICiiService _ciiService; + private readonly ILogger _logger; + + public PPONService(ILogger logger, IServiceScopeFactory factory, + PPONAppSettings appSettings, IDateTimeService dataTimeService, + IHttpClientFactory httpClientFactory) + { + _logger = logger; + _appSettings = appSettings; + _dataTimeService = dataTimeService; + _httpClientFactory = httpClientFactory; + _ciiService = factory.CreateScope().ServiceProvider.GetRequiredService(); + _dataContext = factory.CreateScope().ServiceProvider.GetRequiredService(); + } + + public async Task PerformJob(bool oneTimeValidationSwitch, DateTime startDate, DateTime endDate) + { + try + { + var listOfRegisteredOrgs = await GetRegisteredOrgsAsync(oneTimeValidationSwitch, startDate, endDate); + + if (listOfRegisteredOrgs == null || listOfRegisteredOrgs.Count() == 0) + { + _logger.LogInformation("No Organisation found"); + return; + } + + _logger.LogInformation($"Number of organisation {listOfRegisteredOrgs.Count()}"); + + foreach (var orgDetails in listOfRegisteredOrgs) + { + _logger.LogInformation("------------------------------------------------------------------------------------"); + await ProcessOrg(orgDetails); + _logger.LogInformation("------------------------------------------------------------------------------------"); + } + } + catch (Exception ex) + { + _logger.LogError($"*****Outer Exception during this schedule, exception message = {ex.Message}"); + } + } + + private async Task ProcessOrg(Organisation orgDetails) + { + var ciiOrgId = orgDetails.CiiOrganisationId; + var orgLegalName = orgDetails.LegalName; + + try + { + _logger.LogInformation($"Org: {ciiOrgId + " - " + orgLegalName}"); + + await LinkPPONWithOrgAsync(ciiOrgId); + } + catch (Exception ex) + { + _logger.LogError($"*****Inner Exception while processing the org: {ciiOrgId}, exception message = {ex.Message}"); + } + } + + private async Task> GetRegisteredOrgsAsync(bool oneTimeValidationSwitch, DateTime startDate, DateTime endDate) + { + var dataDuration = _appSettings.ScheduleJobSettings.DataDurationInMinutes; + var untilDateTime = _dataTimeService.GetUTCNow().AddMinutes(-dataDuration); + + try + { + if (oneTimeValidationSwitch) + { + return await GetRegisteredOrgsByDateAsync(startDate, endDate); + } + else + { + return await GetRegisteredOrgsByDateAsync(untilDateTime); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error"); + throw; + } + } + + private async Task> GetRegisteredOrgsByDateAsync(DateTime untilDateTime) + { + var result = await _dataContext.Organisation.Where( + org => !org.IsDeleted && org.IsActivated).ToListAsync(); + + return result.Where(org => org.CreatedOnUtc > untilDateTime) + .Select(o => + new Organisation + { + Id = o.Id, + CiiOrganisationId = o.CiiOrganisationId, + LegalName = o.LegalName, + CreatedOnUtc = o.CreatedOnUtc, + RightToBuy = o.RightToBuy, + SupplierBuyerType = o.SupplierBuyerType + }).ToList(); + } + + private async Task> GetRegisteredOrgsByDateAsync(DateTime startDate, DateTime endDate) + { + return await _dataContext.Organisation.Where( + org => !org.IsDeleted && org.IsActivated + && org.CreatedOnUtc >= TimeZoneInfo.ConvertTimeToUtc(startDate) && org.CreatedOnUtc <= TimeZoneInfo.ConvertTimeToUtc(endDate)) + .Select(o => new Organisation + { + Id = o.Id, + CiiOrganisationId = o.CiiOrganisationId, + LegalName = o.LegalName, + CreatedOnUtc = o.CreatedOnUtc, + RightToBuy = o.RightToBuy, + SupplierBuyerType = o.SupplierBuyerType + }).ToListAsync(); + } + + private async Task LinkPPONWithOrgAsync(string ciiOrganisationId) + { + try + { + _logger.LogInformation("Try to get organisation details from CII"); + + var isExist = await IsPPONExistsAsync(ciiOrganisationId); + + if (isExist) + { + _logger.LogInformation("PPON Id already exists"); + } + else + { + var pPONDetails = await GetPPONDetailsAsync(ciiOrganisationId); + await UpdatePPONAsync(ciiOrganisationId, pPONDetails); + + _logger.LogInformation("PPON Id updated successfully"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error"); + throw; + } + } + + private async Task UpdatePPONAsync(string ciiOrganisationId, PPONDetails pPONDetails) + { + if (pPONDetails != null && pPONDetails.Identifiers != null && pPONDetails.Identifiers.Count > 0) + { + var id = pPONDetails.Identifiers.FirstOrDefault().Id; + _logger.LogInformation("PPON Id: {0}", id); + await _ciiService.AddSchemeAsync(ciiOrganisationId, "GB-PPG", id, null); + } + else + { + _logger.LogInformation("PPON Id not found"); + } + } + + private async Task IsPPONExistsAsync(string ciiOrganisationId) + { + var isExist = false; + + _logger.LogInformation("Try to get organisation details from CII"); + + CiiDto organisationInfo = await _ciiService.GetOrgDetailsAsync(ciiOrganisationId); + + if (organisationInfo != null) + { + _logger.LogInformation("Organisation found on CII database"); + + isExist = organisationInfo?.AdditionalIdentifiers?.Any(x => x.Scheme == "GB-PPG") ?? false; + } + else + { + _logger.LogInformation("Organisation not found on CII database"); + } + + return isExist; + } + + private async Task GetPPONDetailsAsync(string ciiOrganisationId) + { + var client = _httpClientFactory.CreateClient("PPONApi"); + var response = await client.PostAsync("identifiers/id/ppon", null); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + else + { + _logger.LogError("Error while getting PPON details: {0}", response.StatusCode); + throw new Exception("ERROR_RETRIEVING_PPON_DETAILS"); + } + } + } +} + diff --git a/api/CcsSso.Core.PPONScheduler/appsecrets.json b/api/CcsSso.Core.PPONScheduler/appsecrets.json new file mode 100644 index 00000000..1ebb2336 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/appsecrets.json @@ -0,0 +1,23 @@ +{ + "DbConnection": "", + "PPONApi": { + "Key": "", + "Url": "" + }, + "CIIApi": { + "Token": "", + "Url": "" + }, + "ScheduleJob": { + "ScheduleInMinutes": 60, + "DataDurationInMinutes": 60 + }, + "OneTimeJob": { + "Switch": true, + "_Comments_Switch": "scheduled job will not run if it is true.", + "StartDate": "2000-01-01 00:00", + "_Comments_StartDate": "yyyy-MM-dd HH:mm", + "EndDate": "2000-01-01 23:59", + "_Comments_EndDate": "yyyy-MM-dd HH:mm" + } +} \ No newline at end of file diff --git a/api/CcsSso.Core.PPONScheduler/appsettings.Development.json b/api/CcsSso.Core.PPONScheduler/appsettings.Development.json new file mode 100644 index 00000000..b0e35d1c --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Error", + "Microsoft.Hosting.Lifetime": "Error" + } + } +} diff --git a/api/CcsSso.Core.PPONScheduler/appsettings.json b/api/CcsSso.Core.PPONScheduler/appsettings.json new file mode 100644 index 00000000..849703e4 --- /dev/null +++ b/api/CcsSso.Core.PPONScheduler/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Error", + "Microsoft.Hosting.Lifetime": "Error" + } + }, + "VaultEnabled": false, + "Source": "AWS", + "_Comments_Source": "AWS or Hashicorp", + "Vault": { + "Address": "" + } +} diff --git a/api/CcsSso.Core.Service/CiiService.cs b/api/CcsSso.Core.Service/CiiService.cs index 5178b787..06373d3b 100644 --- a/api/CcsSso.Core.Service/CiiService.cs +++ b/api/CcsSso.Core.Service/CiiService.cs @@ -43,7 +43,10 @@ public CiiService(CiiConfig config, IAuditLoginService auditLoginService, IHttpC public async Task AddSchemeAsync(string ciiOrganisationId, string scheme, string identifier, string token) { var client = _httpClientFactory.CreateClient("CiiApi"); - client.DefaultRequestHeaders.Add("Authorization", token); + if (!string.IsNullOrEmpty(token)) + { + client.DefaultRequestHeaders.Add("Authorization", token); + } //var body = JsonConvert.SerializeObject(model); var response = await client.PutAsync($"identities/organisations/{ciiOrganisationId}/schemes/{scheme}/identifiers/{identifier}", new StringContent("", System.Text.Encoding.UTF8, "application/json")); if (response.IsSuccessStatusCode) diff --git a/api/CcsSso.Core.Service/External/OrganisationGroupService.cs b/api/CcsSso.Core.Service/External/OrganisationGroupService.cs index 4ab8b0ba..ef5130a6 100644 --- a/api/CcsSso.Core.Service/External/OrganisationGroupService.cs +++ b/api/CcsSso.Core.Service/External/OrganisationGroupService.cs @@ -1,3 +1,4 @@ +using CcsSso.Core.DbModel.Constants; using CcsSso.Core.DbModel.Entity; using CcsSso.Core.Domain.Contracts; using CcsSso.Core.Domain.Contracts.External; @@ -8,13 +9,16 @@ using CcsSso.Domain.Contracts; using CcsSso.Domain.Dtos; using CcsSso.Domain.Exceptions; +using CcsSso.Shared.Cache.Contracts; using CcsSso.Shared.Domain.Constants; using CcsSso.Shared.Domain.Helpers; using Microsoft.EntityFrameworkCore; +using StackExchange.Redis; using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace CcsSso.Core.Service.External @@ -29,11 +33,16 @@ public class OrganisationGroupService : IOrganisationGroupService private readonly ApplicationConfigurationInfo _appConfigInfo; private readonly IServiceRoleGroupMapperService _serviceRoleGroupMapperService; private readonly IOrganisationProfileService _organisationService; + private readonly IUserProfileRoleApprovalService _userProfileRoleApprovalService; + private readonly ILocalCacheService _localCacheService; + public OrganisationGroupService(IDataContext dataContext, IUserProfileHelperService userProfileHelperService, IAuditLoginService auditLoginService, ICcsSsoEmailService ccsSsoEmailService, IWrapperCacheService wrapperCacheService, ApplicationConfigurationInfo appConfigInfo, IServiceRoleGroupMapperService serviceRoleGroupMapperService, - IOrganisationProfileService organisationService) + IOrganisationProfileService organisationService, + IUserProfileRoleApprovalService userProfileRoleApprovalService, + ILocalCacheService localCacheService) { _dataContext = dataContext; _userProfileHelperService = userProfileHelperService; @@ -43,6 +52,8 @@ public OrganisationGroupService(IDataContext dataContext, IUserProfileHelperServ _appConfigInfo = appConfigInfo; _serviceRoleGroupMapperService = serviceRoleGroupMapperService; _organisationService = organisationService; + _userProfileRoleApprovalService = userProfileRoleApprovalService; + _localCacheService = localCacheService; } public async Task CreateGroupAsync(string ciiOrganisationId, OrganisationGroupNameInfo organisationGroupNameInfo) @@ -116,6 +127,8 @@ public async Task DeleteGroupAsync(string ciiOrganisationId, int groupId) await _dataContext.SaveChangesAsync(); + await RemoveGroupRolePendingRequest(group); + // Log await _auditLoginService.CreateLogAsync(AuditLogEvent.GroupeDelete, AuditLogApplication.ManageGroup, $"GroupId:{group.Id}, GroupName:{group.UserGroupName}, OrganisationId:{ciiOrganisationId}"); @@ -149,18 +162,34 @@ public async Task GetGroupAsync(string ciiOrganis { Id = gr.OrganisationEligibleRole.Id, Name = gr.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName - }).ToList(), - Users = group.UserGroupMemberships.Where(ugm => !ugm.IsDeleted).Select(ugm => new GroupUser - { - UserId = ugm.User.UserName, - Name = $"{ugm.User.Party.Person.FirstName} {ugm.User.Party.Person.LastName}", - IsAdmin = ugm.User.UserAccessRoles.Any(r => !r.IsDeleted && r.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey == Contstant.OrgAdminRoleNameKey && !r.OrganisationEligibleRole.IsDeleted) }).ToList() + }; + var isApprovalRequired = group.GroupEligibleRoles.Any(x => !x.OrganisationEligibleRole.IsDeleted && x.OrganisationEligibleRole.CcsAccessRole.ApprovalRequired == 1); + + organisationGroupResponseInfo.Users = group.UserGroupMemberships.Where(ugm => !ugm.IsDeleted).Select(ugm => new GroupUser + { + UserId = ugm.User.UserName, + Name = $"{ugm.User.Party.Person.FirstName} {ugm.User.Party.Person.LastName}", + IsAdmin = ugm.User.UserAccessRoles.Any(r => !r.IsDeleted && r.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey == Contstant.OrgAdminRoleNameKey && !r.OrganisationEligibleRole.IsDeleted), + + UserPendingRoleStatus = !isApprovalRequired ? null : getUserRolePendingStatus(ugm), + }).ToList(); return organisationGroupResponseInfo; } + private UserPendingRoleStaus? getUserRolePendingStatus(UserGroupMembership ugm) + { + // return _dataContext.UserAccessRolePending.OrderByDescending(y => y.Id).FirstOrDefault(x => !x.IsDeleted && x.UserId == ugm.User.Id && x.Status == (int)UserPendingRoleStaus.Pending) != null; + var pendingRole = _dataContext.UserAccessRolePending + .OrderByDescending(y => y.Id) + .FirstOrDefault(x => x.UserId == ugm.User.Id && x.OrganisationUserGroupId == ugm.OrganisationUserGroupId); + + + return (UserPendingRoleStaus?)(pendingRole?.Status); + } + public async Task GetGroupsAsync(string ciiOrganisationId, string searchString = null) { var organisation = await _dataContext.Organisation @@ -191,7 +220,7 @@ public async Task GetGroupsAsync(string ciiOrganisationId public async Task UpdateGroupAsync(string ciiOrganisationId, int groupId, OrganisationGroupRequestInfo organisationGroupRequestInfo) { var group = await _dataContext.OrganisationUserGroup - .Include(g => g.GroupEligibleRoles).ThenInclude(r => r.OrganisationEligibleRole) + .Include(g => g.GroupEligibleRoles).ThenInclude(r => r.OrganisationEligibleRole).ThenInclude(x => x.CcsAccessRole) .Include(g => g.UserGroupMemberships).ThenInclude(ugm => ugm.User) .FirstOrDefaultAsync(g => !g.IsDeleted && g.Id == groupId && g.Organisation.CiiOrganisationId == ciiOrganisationId); @@ -294,7 +323,6 @@ public async Task UpdateGroupAsync(string ciiOrganisationId, int groupId, Organi OrganisationUserGroupId = groupId, OrganisationEligibleRoleId = addedRoleId }; - group.GroupEligibleRoles.Add(groupAccess); } }); @@ -349,11 +377,33 @@ public async Task UpdateGroupAsync(string ciiOrganisationId, int groupId, Organi UserId = addedUserId, OrganisationUserGroupId = group.Id }; - addedUsersTupleList.Add(new Tuple(addedUserId, addedUserName)); // for logs group.UserGroupMemberships.Add(userGroupMembership); } }); + + // this will be used in the success page. (Group success page only shows pending status for the added users) + var expiration = new TimeSpan(0, 0, 60); + if (addedUserNameList.Any()) + { + _localCacheService.SetValue(groupId.ToString(), addedUserNameList, expiration); + } + else + { + if (removedUserNameList.Any()) + { + _localCacheService.SetValue(groupId.ToString(), new List { "userremoved" }, expiration); + } + else + { + _localCacheService.Remove(new string[] { groupId.ToString() }); + } + } + } + else + { + _localCacheService.Remove(new string[] { groupId.ToString() }); + } var mfaEnableRoleExists = orgRoleInfo.Any(r => group.GroupEligibleRoles.Any(ge => ge.OrganisationEligibleRoleId == r.Id && !ge.IsDeleted && r.MfaEnable)); @@ -372,6 +422,13 @@ public async Task UpdateGroupAsync(string ciiOrganisationId, int groupId, Organi group.MfaEnabled = mfaEnableRoleExists; await _dataContext.SaveChangesAsync(); + if (_appConfigInfo.UserRoleApproval.Enable) + { + await RemoveGroupRolesApproveRequest(groupId, removedRoleIds); + await RemoveGroupUsersApproveRequest(groupId, removedUsersTupleList); + await VerifyAndCreateGroupRolePendingRequest(group, ciiOrganisationId, addedUsersTupleList, addedRoleIds); + } + //Log if (hasNameChanged) { @@ -416,6 +473,156 @@ await _auditLoginService.CreateLogAsync(AuditLogEvent.GroupeUserRemove, AuditLog await _wrapperCacheService.RemoveCacheAsync(invalidatingCacheKeys.ToArray()); } + private async Task RemoveGroupUsersApproveRequest(int groupId, List> removedUsersTupleList) + { + if (removedUsersTupleList != null && removedUsersTupleList.Any()) + { + var removedUserIds = removedUsersTupleList.Select(x => x.Item1); + + var userAccessRolePendingList = await _dataContext.UserAccessRolePending + .Where(x => removedUserIds.Contains(x.UserId) + && x.OrganisationUserGroupId == groupId).ToListAsync(); + + userAccessRolePendingList.ForEach(l => { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Removed; }); + + await _dataContext.SaveChangesAsync(); + } + } + + private async Task RemoveGroupRolesApproveRequest(int groupId, List removedRoleIds) + { + if (removedRoleIds != null && removedRoleIds.Any()) + { + var userAccessRolePendingList = await _dataContext.UserAccessRolePending + .Where(x => removedRoleIds.Contains(x.OrganisationEligibleRoleId) + && x.OrganisationUserGroupId == groupId).ToListAsync(); + + userAccessRolePendingList.ForEach(l => { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Removed; }); + + await _dataContext.SaveChangesAsync(); + } + } + + private async Task VerifyAndCreateGroupRolePendingRequest(OrganisationUserGroup group, string ciiOrganisationId, List> addedUsersTupleList, List addedRoleIds) + { + if (!_appConfigInfo.UserRoleApproval.Enable) + { + return; + } + var org = await _dataContext.Organisation.FirstOrDefaultAsync(x => x.CiiOrganisationId == ciiOrganisationId); + var orgDomain = org?.DomainName?.ToLower(); + + List newAddedUsers = new(); + if (group.GroupEligibleRoles.Any(x => !x.IsDeleted && addedRoleIds.Contains(x.OrganisationEligibleRoleId) && + x.OrganisationEligibleRole.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalRequired)) + { + newAddedUsers = group.UserGroupMemberships.Where(x => !x.IsDeleted).Select(ugm => ugm.User).ToList(); + } + else + { + newAddedUsers = group.UserGroupMemberships.Where(x => !x.IsDeleted).Select(ugm => ugm.User).Where(u => addedUsersTupleList.Select(x => x.Item1).Contains(u.Id)).ToList(); + } + var userHasInValidDomain = newAddedUsers.Where(user => user.UserName.ToLower().Split('@')?[1] != orgDomain).ToList(); + + if (userHasInValidDomain.Any()) + { + //await RemoveGroupRolePendingRequest(group, userHasInValidDomain); + + List approvalRequiredRoles = new(); + foreach (var role in group.GroupEligibleRoles.Where(x => !x.IsDeleted)) + { + if (role.OrganisationEligibleRole.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalRequired) + { + approvalRequiredRoles.Add(role.OrganisationEligibleRoleId); + } + } + + if (approvalRequiredRoles.Any()) + { + await GeneratePendingRequests(group, userHasInValidDomain, approvalRequiredRoles); + } + else + { + // remove any pending request exists for this group + //await RemoveGroupRolePendingRequest(group); + } + } + else + { + // remove any pending request exists for this group + //await RemoveGroupRolePendingRequest(group); + } + } + + private async Task RemoveGroupRolePendingRequest(OrganisationUserGroup group) + { + var pendingGroupRequest = await _dataContext.UserAccessRolePending.Where(x => + (x.Status == (int)UserPendingRoleStaus.Pending + || x.Status == (int)UserPendingRoleStaus.Approved + || x.Status == (int)UserPendingRoleStaus.Rejected) && + x.OrganisationUserGroupId == group.Id).ToListAsync(); + + foreach (var pendingRequest in pendingGroupRequest) + { + pendingRequest.IsDeleted = true; + pendingRequest.Status = (int)UserPendingRoleStaus.Removed; + } + await _dataContext.SaveChangesAsync(); + } + + public async Task GetGroupUsersPendingRequestSummary(int groupId, string ciiOrgId, ResultSetCriteria resultSetCriteria, bool isPendingApproval) + { + var group = await _dataContext.OrganisationUserGroup + .Include(g => g.UserGroupMemberships).ThenInclude(ugm => ugm.User) + .FirstOrDefaultAsync(g => !g.IsDeleted && g.Id == groupId && g.Organisation.CiiOrganisationId == ciiOrgId); + + if (group == null) + { + throw new ResourceNotFoundException(); + } + + var existingUserIds = group.UserGroupMemberships.Where(x => !x.IsDeleted).Select(ugm => ugm.UserId); + + var pendingAndRejectedRequests = await _dataContext.UserAccessRolePending + .Where(x => existingUserIds.Contains(x.UserId) && x.OrganisationUserGroupId == groupId + && (x.Status == (int)UserPendingRoleStaus.Pending || x.Status == (int)UserPendingRoleStaus.Rejected)) + .ToListAsync(); + + var pendingRequests = pendingAndRejectedRequests.Where(x => x.Status == (int)UserPendingRoleStaus.Pending && !x.IsDeleted); + + + var filteredUserIds = isPendingApproval ? existingUserIds.Where(x => pendingRequests.Any(y => y.UserId == x)) : existingUserIds.Where(x => !pendingAndRejectedRequests.Any(y => y.UserId == x)); + + var addedUsers = _localCacheService.GetValue>(groupId.ToString()); + if (addedUsers != null && addedUsers.Any()) + { + var userIds = _dataContext.User.Where(x => addedUsers.Contains(x.UserName)).Select(x => x.Id); + filteredUserIds = filteredUserIds.Where(x => userIds.Contains(x)).ToArray(); + } + + var usersQuery = _dataContext.User.Include(u => u.Party).ThenInclude(p => p.Person).Where(user => !user.IsDeleted && filteredUserIds.Contains(user.Id)).OrderBy(u => u.UserName); + + var pagedResult = await _dataContext.GetPagedResultAsync(usersQuery, resultSetCriteria); + + var groupUserListResponse = new GroupUserListResponse + { + groupId = groupId, + CurrentPage = pagedResult.CurrentPage, + PageCount = pagedResult.PageCount, + RowCount = pagedResult.RowCount, + GroupUser = pagedResult.Results?.Select(up => new GroupUser + { + UserId = up.UserName, + UserPendingRoleStatus = isPendingApproval ? UserPendingRoleStaus.Pending : null, // pending and rejected will be shown as users doesn't have the role. + Name = $"{up.Party.Person.FirstName} {up.Party.Person.LastName}", + }).ToList() ?? new List() + }; + + return groupUserListResponse; + } + + + public async Task GetServiceRoleGroupAsync(string ciiOrganisationId, int groupId) { if (!_appConfigInfo.ServiceRoleGroupSettings.Enable) @@ -478,7 +685,7 @@ public async Task UpdateServiceRoleGroupAsync(string ciiOrganisationId, int grou }; await this.UpdateGroupAsync(ciiOrganisationId, groupId, organisationGroupRequestInfo); - } + } private static OrganisationServiceRoleGroupResponseInfo ConvertGroupRoleToServiceRoleGroupResponse(OrganisationGroupResponseInfo organisationGroupResponseInfo) { @@ -527,5 +734,35 @@ private async Task> ConvertServiceRoleGroupsToOrganisationRoleIds(stri return roleIds; } + + private async Task GeneratePendingRequests(OrganisationUserGroup group, List userHasInValidDomain, List approvalRequiredRoles) + { + var existingUsersRequests = await _dataContext.UserAccessRolePending.Where(x => approvalRequiredRoles.Contains(x.OrganisationEligibleRoleId) + && x.OrganisationUserGroupId == group.Id + && userHasInValidDomain.Select(u => u.Id).Contains(x.UserId)).ToListAsync(); + + foreach (var user in userHasInValidDomain) + { + var latestRequestOfUser = existingUsersRequests.OrderByDescending(x => x.Id).FirstOrDefault(x => x.UserId == user.Id); + + if (latestRequestOfUser == null || + (latestRequestOfUser.Status != (int)UserPendingRoleStaus.Pending + && latestRequestOfUser.Status != (int)UserPendingRoleStaus.Approved + && latestRequestOfUser.Status != (int)UserPendingRoleStaus.Rejected)) + { + await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new UserProfileEditRequestInfo + { + UserName = user.UserName, + OrganisationId = group.Organisation.CiiOrganisationId, + Detail = new UserRequestDetail + { + GroupId = group.Id, + RoleIds = approvalRequiredRoles + } + }, sendEmailNotification: false); + } + } + } + } } diff --git a/api/CcsSso.Core.Service/External/OrganisationProfileService.cs b/api/CcsSso.Core.Service/External/OrganisationProfileService.cs index 184758aa..49141a7e 100644 --- a/api/CcsSso.Core.Service/External/OrganisationProfileService.cs +++ b/api/CcsSso.Core.Service/External/OrganisationProfileService.cs @@ -1138,7 +1138,7 @@ private async Task AutoValidateForValidDomain(Organisation organisation, U //for admin roles if (isFromBackgroundJob) { - await AutoValidationAdminRolesForBackgroundJob(organisation, schemeIdentifier, groupId, auditEventLogs, adminUserDetails,true); + await AutoValidationAdminRolesForBackgroundJob(organisation, schemeIdentifier, groupId, auditEventLogs, adminUserDetails, true); } else { @@ -1219,7 +1219,7 @@ private async Task AutoValidateForInValidDomain(Organisation organisation, //for admin roles if (isFromBackgroundJob) { - await AutoValidationAdminRolesForBackgroundJob(organisation, schemeIdentifier, groupId, auditEventLogs, adminUserDetails,false); + await AutoValidationAdminRolesForBackgroundJob(organisation, schemeIdentifier, groupId, auditEventLogs, adminUserDetails, false); } else { @@ -1324,7 +1324,7 @@ private async Task AutoValidationAdminRoleAssignmentAsync(User adminDeta roleIds = roleIds.Union(successAdminRoleIds); } - var defaultRoles = await _dataContext.OrganisationEligibleRole + var defaultRoles = await _dataContext.OrganisationEligibleRole .Where(r => r.Organisation.CiiOrganisationId == ciiOrganisation && !r.IsDeleted && roleIds.Contains(r.CcsAccessRoleId)) .ToListAsync(); @@ -1589,22 +1589,10 @@ private async Task RemoveOrgRoles(List rolesToDelete, var userAccessRolesWithDeletedRoles = userAccessRolesForOrgUsers .Where(uar => deletingRoleIds.Contains(uar.OrganisationEligibleRole.CcsAccessRoleId)).ToList(); - var allAccessRolePending = await _dataContext.UserAccessRolePending.Where(u => !u.IsDeleted && u.Status == (int)UserPendingRoleStaus.Pending).ToListAsync(); deletingOrgEligibleRoles.ForEach((deletingOrgEligibleRole) => { - if (_applicationConfigurationInfo.UserRoleApproval.Enable) - { - var pendingRequests = allAccessRolePending.Where(x => x.OrganisationEligibleRoleId == deletingOrgEligibleRole.Id).ToList(); - foreach (var pendingRequest in pendingRequests) - { - pendingRequest.IsDeleted = true; - pendingRequest.Status = (int)UserPendingRoleStaus.Removed; - } - } - deletingOrgEligibleRole.IsDeleted = true; rolesRemoved.Append(rolesRemoved.Length > 0 ? "," + deletingOrgEligibleRole.CcsAccessRole.CcsAccessRoleName : deletingOrgEligibleRole.CcsAccessRole.CcsAccessRoleName); - }); orgGroupRolesWithDeletedRoles.ForEach((orgGroupRolesWithDeletedRole) => @@ -1616,6 +1604,18 @@ private async Task RemoveOrgRoles(List rolesToDelete, { userAccessRolesWithDeletedRole.IsDeleted = true; }); + + if (_applicationConfigurationInfo.UserRoleApproval.Enable) + { + var deletingOrgEligibleRoleIds = deletingOrgEligibleRoles.Select(x => x.Id); + var allAccessRolePending = await _dataContext.UserAccessRolePending.Where(u => deletingOrgEligibleRoleIds.Contains(u.OrganisationEligibleRoleId)).ToListAsync(); + + allAccessRolePending.ForEach((pendingRequest) => + { + pendingRequest.IsDeleted = true; + pendingRequest.Status = (int)UserPendingRoleStaus.Removed; + }); + } } return rolesRemoved.ToString(); @@ -1695,6 +1695,16 @@ private async Task AdminRoleAssignment(Organisation organisation, RoleEligibleTr } else { + // Check any approved and pending request are there for the user + var anyExistingRoleRequest = await _dataContext.UserAccessRolePending.AnyAsync(x => x.OrganisationEligibleRoleId == organisationEligibleRoleId + && x.OrganisationUserGroupId == null + && x.UserId == adminDetails.Id + && (x.Status == (int)UserPendingRoleStaus.Approved)); + + if (anyExistingRoleRequest) + { + continue; + } await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new UserProfileEditRequestInfo { UserName = adminDetails.UserName, @@ -2094,30 +2104,30 @@ private async Task ManualValidateAdminRoleAssignmentAsync(Organisation organisat await _dataContext.SaveChangesAsync(); } - private async Task AssignRoleToAllOrgAdmins(OrganisationEligibleRole role, List allAdminsOfOrg, Organisation organisation, List servicesWithApprovalRequiredRole) + private async Task AssignRoleToAllOrgAdmins(OrganisationEligibleRole role, List allAdminsOfOrg, Organisation organisation, List servicesWithApprovalRequiredRole) { foreach (var adminDetails in allAdminsOfOrg) { if (!adminDetails.UserAccessRoles.Any(x => x.OrganisationEligibleRoleId == role.Id && !x.IsDeleted)) { var isAdminDomainSameAsOrg = adminDetails.UserName.ToLower().Split('@')?[1] == organisation.DomainName?.ToLower(); - + // Remove normals roles which are part of service which required role approval // They will be assigned together with role approval. if (_applicationConfigurationInfo.UserRoleApproval.Enable && _applicationConfigurationInfo.ServiceRoleGroupSettings.Enable && - !isAdminDomainSameAsOrg && RoleBelongToApprovalRequiredService(role, servicesWithApprovalRequiredRole)) + !isAdminDomainSameAsOrg && RoleBelongToApprovalRequiredService(role, servicesWithApprovalRequiredRole)) { continue; } - if (!_applicationConfigurationInfo.UserRoleApproval.Enable || role.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalNotRequired || + if (!_applicationConfigurationInfo.UserRoleApproval.Enable || role.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalNotRequired || isAdminDomainSameAsOrg) { var defaultUserRole = new UserAccessRole { OrganisationEligibleRoleId = role.Id }; - adminDetails.UserAccessRoles.Add(defaultUserRole); + adminDetails.UserAccessRoles.Add(defaultUserRole); } else { @@ -2155,16 +2165,16 @@ public async Task> GetOrganisationServiceRoleGroupsAsync( var orgRoles = await GetOrganisationRolesAsync(ciiOrganisationId); var serviceRoleGroupsEntity = await _rolesToServiceRoleGroupMapperService.OrgRolesToServiceRoleGroupsAsync(orgRoles.Select(x => x.RoleId).ToList()); var serviceRoleGroups = serviceRoleGroupsEntity.Select(x => new ServiceRoleGroup - { - Id = x.Id, - Key = x.Key, - Name = x.Name, - OrgTypeEligibility = x.OrgTypeEligibility, - SubscriptionTypeEligibility = x.SubscriptionTypeEligibility, - TradeEligibility = x.TradeEligibility, - DisplayOrder = x.DisplayOrder, - Description = x.Description - }).ToList(); + { + Id = x.Id, + Key = x.Key, + Name = x.Name, + OrgTypeEligibility = x.OrgTypeEligibility, + SubscriptionTypeEligibility = x.SubscriptionTypeEligibility, + TradeEligibility = x.TradeEligibility, + DisplayOrder = x.DisplayOrder, + Description = x.Description + }).ToList(); return serviceRoleGroups; } @@ -2175,7 +2185,7 @@ public async Task UpdateOrganisationEligibleServiceRoleGroupsAsync(string ciiOrg throw new InvalidOperationException(); } - if (!ValidateCiiOrganisationID(ciiOrganisationId) || serviceRoleGroupsToAdd == null || serviceRoleGroupsToDelete == null) + if (!ValidateCiiOrganisationID(ciiOrganisationId) || serviceRoleGroupsToAdd == null || serviceRoleGroupsToDelete == null) { throw new CcsSsoException(ErrorConstant.ErrorInvalidDetails); } @@ -2189,7 +2199,7 @@ public async Task UpdateOrganisationEligibleServiceRoleGroupsAsync(string ciiOrg await UpdateOrganisationEligibleRolesAsync(ciiOrganisationId, isBuyer, addRoles, deleteRoles); } - public async Task UpdateOrgAutoValidServiceRoleGroupsAsync(string ciiOrganisationId, RoleEligibleTradeType newOrgType, List serviceRoleGroupsToAdd, List serviceRoleGroupsToDelete, List serviceRoleGroupsToAutoValid, string? companyHouseId) + public async Task UpdateOrgAutoValidServiceRoleGroupsAsync(string ciiOrganisationId, RoleEligibleTradeType newOrgType, List serviceRoleGroupsToAdd, List serviceRoleGroupsToDelete, List serviceRoleGroupsToAutoValid, string? companyHouseId) { if (!_applicationConfigurationInfo.ServiceRoleGroupSettings.Enable) { @@ -2212,13 +2222,13 @@ public async Task UpdateOrgAutoValidServiceRoleGroupsAsync(string ciiOrganisatio await UpdateOrgAutoValidationEligibleRolesAsync(ciiOrganisationId, newOrgType, addRoles, deleteRoles, autoValidRoles, companyHouseId); } - private static bool RoleBelongToApprovalRequiredService(OrganisationEligibleRole role, List servicesWithApprovalRequiredRole) + private static bool RoleBelongToApprovalRequiredService(OrganisationEligibleRole role, List servicesWithApprovalRequiredRole) { foreach (var approvalRoleService in servicesWithApprovalRequiredRole) { var removeRoles = approvalRoleService.CcsServiceRoleMappings.Where(x => x.CcsAccessRole.ApprovalRequired != 1).Select(x => x.CcsAccessRoleId).ToList(); // Return true for normal role that belongs to approval required service - if (removeRoles.Any(x => x == role.CcsAccessRoleId)) + if (removeRoles.Any(x => x == role.CcsAccessRoleId)) { return true; } diff --git a/api/CcsSso.Core.Service/External/ServiceRoleGroupMapperService.cs b/api/CcsSso.Core.Service/External/ServiceRoleGroupMapperService.cs index aa852b19..ced9f630 100644 --- a/api/CcsSso.Core.Service/External/ServiceRoleGroupMapperService.cs +++ b/api/CcsSso.Core.Service/External/ServiceRoleGroupMapperService.cs @@ -1,4 +1,4 @@ -using CcsSso.Core.DbModel.Constants; +using CcsSso.Core.DbModel.Constants; using CcsSso.Core.DbModel.Entity; using CcsSso.Core.Domain.Contracts.External; using CcsSso.DbModel.Entity; @@ -117,14 +117,14 @@ public async Task> ServiceRoleGroupsWithApprovalRequir { var serviceRoleGroups = await _dataContext.CcsServiceRoleGroup .Include(g => g.CcsServiceRoleMappings).ThenInclude(g => g.CcsAccessRole) - .Where(x => !x.IsDeleted && x.CcsServiceRoleMappings.Any(y => y.CcsAccessRole.ApprovalRequired == 1)).ToListAsync(); + .Where(x => !x.IsDeleted && x.CcsServiceRoleMappings.Any(y => y.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalRequired)).ToListAsync(); return serviceRoleGroups; } // This method will remove roles that are part of approval required Service Role Group but it self not required approval // This normal roles will be assigned together with approval required role, once it is approved. - public async Task RemoveApprovalRequiredRoleGroupOtherRolesAsync(List organisationEligibleRoles, string userName) + public async Task RemoveApprovalRequiredRoleGroupOtherRolesAsync(List organisationEligibleRoles, string userName) { var servicesWithApprovalRequiredRole = await ServiceRoleGroupsWithApprovalRequiredRoleAsync(); var userExistingRoles = await _dataContext.User @@ -137,7 +137,7 @@ public async Task RemoveApprovalRequiredRoleGroupOtherRolesAsync(List x.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalNotRequired).Select(x => x.CcsAccessRoleId).ToList(); - foreach (var removeRole in removeRoles) + foreach (var removeRole in removeRoles) { if (!userExistingRoles.Any(u => u.UserAccessRoles.Any(a => !a.IsDeleted && a.OrganisationEligibleRole.CcsAccessRoleId == removeRole))) { @@ -156,4 +156,4 @@ private async Task> OrgRolesToCcsRoles(List roleIds) .ToListAsync(); } } -} +} \ No newline at end of file diff --git a/api/CcsSso.Core.Service/External/UserProfileRoleApprovalService.cs b/api/CcsSso.Core.Service/External/UserProfileRoleApprovalService.cs index 404e3b5a..f0adf3d2 100644 --- a/api/CcsSso.Core.Service/External/UserProfileRoleApprovalService.cs +++ b/api/CcsSso.Core.Service/External/UserProfileRoleApprovalService.cs @@ -47,7 +47,6 @@ public async Task UpdateUserRoleStatusAsync(UserRoleApprovalEditRequest us var pendingRoleIds = userApprovalRequest.PendingRoleIds; var status = userApprovalRequest.Status; - var serviceName = String.Empty; if (status != UserPendingRoleStaus.Approved && status != UserPendingRoleStaus.Rejected) { @@ -55,7 +54,7 @@ public async Task UpdateUserRoleStatusAsync(UserRoleApprovalEditRequest us } var pendingRole = await _dataContext.UserAccessRolePending - .Where(x => pendingRoleIds.Contains(x.Id) && !x.IsDeleted && x.Status == (int)UserPendingRoleStaus.Pending).ToListAsync(); + .Where(x => pendingRoleIds.Contains(x.Id)).ToListAsync(); if (pendingRole != null && pendingRole.Count() < pendingRoleIds.Length) { @@ -68,13 +67,24 @@ public async Task UpdateUserRoleStatusAsync(UserRoleApprovalEditRequest us foreach (var pendingRoleId in pendingRoleIds) { var pendingUserRole = await _dataContext.UserAccessRolePending.Include(x => x.OrganisationEligibleRole).ThenInclude(r => r.CcsAccessRole) - .FirstOrDefaultAsync(x => x.Id == pendingRoleId && !x.IsDeleted && x.Status == (int)UserPendingRoleStaus.Pending); + .FirstOrDefaultAsync(x => x.Id == pendingRoleId); if (pendingUserRole == null) { throw new CcsSsoException(ErrorConstant.ErrorInvalidRoleInfo); } + var isPendingRequest = !pendingUserRole.IsDeleted && pendingUserRole.Status == (int)UserPendingRoleStaus.Pending; + + var isOtherPendingRequest = await _dataContext.UserAccessRolePending + .AnyAsync(x => !x.IsDeleted && x.UserId == pendingUserRole.UserId && x.Status == (int)UserPendingRoleStaus.Pending + && x.OrganisationEligibleRoleId == pendingUserRole.OrganisationEligibleRoleId && x.Id != pendingUserRole.Id); + + if (!isPendingRequest && !isOtherPendingRequest) + { + throw new CcsSsoException(ErrorConstant.ErrorInvalidRoleInfo); + } + var user = await _dataContext.User .Include(u => u.UserAccessRoles).ThenInclude(u => u.OrganisationEligibleRole) .FirstOrDefaultAsync(x => x.Id == pendingUserRole.UserId && !x.IsDeleted && x.UserType == UserType.Primary); @@ -84,95 +94,144 @@ public async Task UpdateUserRoleStatusAsync(UserRoleApprovalEditRequest us throw new ResourceNotFoundException(); } - if (status == UserPendingRoleStaus.Rejected) + if (isPendingRequest) { - pendingUserRole.Status = (int)UserPendingRoleStaus.Rejected; - pendingUserRole.IsDeleted = true; - + await ApproveRejectRoleRequest(status, serviceRoleGroupsWithApprovalRequiredRole, pendingUserRole, user); + await SendApproveRejectRoleRequestEmail(status, pendingUserRole, user); } - else - { - pendingUserRole.Status = (int)UserPendingRoleStaus.Approved; - pendingUserRole.IsDeleted = true; - var roleAlreadyExists = await _dataContext.UserAccessRole.FirstOrDefaultAsync(x => x.UserId == pendingUserRole.UserId && !x.IsDeleted && x.OrganisationEligibleRoleId == pendingUserRole.OrganisationEligibleRoleId); + await UpdateRemaningRequestsOfUser(status, serviceRoleGroupsWithApprovalRequiredRole, pendingUserRole, user); + } - if (roleAlreadyExists == null) - { - user.UserAccessRoles.Add(new UserAccessRole - { - UserId = user.Id, - OrganisationEligibleRoleId = pendingUserRole.OrganisationEligibleRoleId - }); + return await Task.FromResult(true); + } - // On role approval assign normal roles of service as well - if (_appConfigInfo.ServiceRoleGroupSettings.Enable) - { - var serviceGroup = serviceRoleGroupsWithApprovalRequiredRole.FirstOrDefault(x => x.CcsServiceRoleMappings.Any(r => r.CcsAccessRoleId == pendingUserRole.OrganisationEligibleRole.CcsAccessRoleId)); - var serviceMappingCcsRoleIds = serviceGroup.CcsServiceRoleMappings.Where(y => y.CcsAccessRole.ApprovalRequired == 0).Select(x => x.CcsAccessRoleId).ToList(); - - var allOrgEligibleRoles = await _dataContext.OrganisationEligibleRole.Include(or => or.CcsAccessRole) - .Where(x => !x.IsDeleted && x.OrganisationId == pendingUserRole.OrganisationEligibleRole.OrganisationId && - serviceMappingCcsRoleIds.Contains(x.CcsAccessRoleId)).ToListAsync(); - foreach (var orgRole in allOrgEligibleRoles) - { - if (!user.UserAccessRoles.Any(x => x.OrganisationEligibleRoleId == orgRole.Id && !x.IsDeleted)) - { - user.UserAccessRoles.Add(new UserAccessRole - { - UserId = user.Id, - OrganisationEligibleRoleId = orgRole.Id - }); - } - } + private async Task SendApproveRejectRoleRequestEmail(UserPendingRoleStaus status, UserAccessRolePending pendingUserRole, User user) + { + string serviceName = await GetServiceNameForEmail(pendingUserRole); - } - } + var emailList = new List() { user.UserName }; + if (pendingUserRole.UserId != pendingUserRole.CreatedUserId) + { + var roleRequester = await _dataContext.User + .FirstOrDefaultAsync(x => x.Id == pendingUserRole.CreatedUserId && !x.IsDeleted && x.UserType == UserType.Primary); + + if (roleRequester != null) + { + emailList.Add(roleRequester.UserName); } + } - await _dataContext.SaveChangesAsync(); + if (pendingUserRole.SendEmailNotification) + { + foreach (var email in emailList) + { + if (status == UserPendingRoleStaus.Approved) + await _ccsSsoEmailService.SendRoleApprovedEmailAsync(email, user.UserName, serviceName, _appConfigInfo.ConclaveLoginUrl); + else + await _ccsSsoEmailService.SendRoleRejectedEmailAsync(email, user.UserName, serviceName); + } + } + } - var orgEligibleRole = await _dataContext.OrganisationEligibleRole.Include(or => or.CcsAccessRole) - .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) - .FirstOrDefaultAsync(u => u.Id == pendingUserRole.OrganisationEligibleRoleId! && !u.IsDeleted); + private async Task GetServiceNameForEmail(UserAccessRolePending pendingUserRole) + { + var serviceName = String.Empty; - if (orgEligibleRole != null) + var orgEligibleRole = await _dataContext.OrganisationEligibleRole.Include(or => or.CcsAccessRole) + .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) + .FirstOrDefaultAsync(u => u.Id == pendingUserRole.OrganisationEligibleRoleId! && !u.IsDeleted); + + if (orgEligibleRole != null) + { + serviceName = orgEligibleRole.CcsAccessRole.ServiceRolePermissions.FirstOrDefault()?.ServicePermission.CcsService.ServiceName; + if (_appConfigInfo.ServiceRoleGroupSettings.Enable) { - serviceName = orgEligibleRole.CcsAccessRole.ServiceRolePermissions.FirstOrDefault()?.ServicePermission.CcsService.ServiceName; - if (_appConfigInfo.ServiceRoleGroupSettings.Enable) - { - var roleServiceInfo = await _serviceRoleGroupMapperService.CcsRolesToServiceRoleGroupsAsync(new List() { orgEligibleRole.CcsAccessRoleId }); - serviceName = roleServiceInfo?.FirstOrDefault()?.Name; - } + var roleServiceInfo = await _serviceRoleGroupMapperService.CcsRolesToServiceRoleGroupsAsync(new List() { orgEligibleRole.CcsAccessRoleId }); + serviceName = roleServiceInfo?.FirstOrDefault()?.Name; } + } - var emailList = new List() { user.UserName }; + return serviceName; + } - if (pendingUserRole.UserId != pendingUserRole.CreatedUserId) - { - var roleRequester = await _dataContext.User - .FirstOrDefaultAsync(x => x.Id == pendingUserRole.CreatedUserId && !x.IsDeleted && x.UserType == UserType.Primary); + private async Task UpdateRemaningRequestsOfUser(UserPendingRoleStaus status, List serviceRoleGroupsWithApprovalRequiredRole, UserAccessRolePending pendingUserRole, User user) + { + var userAccessRolePendingRequests = await _dataContext.UserAccessRolePending + .Include(x => x.OrganisationEligibleRole).ThenInclude(r => r.CcsAccessRole) + .Where(x => !x.IsDeleted && x.UserId == pendingUserRole.UserId && x.Status == (int)UserPendingRoleStaus.Pending + && x.OrganisationEligibleRoleId == pendingUserRole.OrganisationEligibleRoleId && x.Id != pendingUserRole.Id) + .ToListAsync(); + + foreach (var userAccessRolePendingRequest in userAccessRolePendingRequests) + { + await ApproveRejectRoleRequest(status, serviceRoleGroupsWithApprovalRequiredRole, userAccessRolePendingRequest, user); + } + } + + private async Task ApproveRejectRoleRequest(UserPendingRoleStaus status, List serviceRoleGroupsWithApprovalRequiredRole, UserAccessRolePending pendingUserRole, User user) + { + if (status == UserPendingRoleStaus.Rejected) + { + UpdateRoleRequestStatus(pendingUserRole, UserPendingRoleStaus.Rejected); + } + else + { + UpdateRoleRequestStatus(pendingUserRole, UserPendingRoleStaus.Approved); + await AssignRequestedRoleToUser(serviceRoleGroupsWithApprovalRequiredRole, pendingUserRole, user); + } + await _dataContext.SaveChangesAsync(); + } - if (roleRequester != null) + private async Task AssignRequestedRoleToUser(List serviceRoleGroupsWithApprovalRequiredRole, UserAccessRolePending pendingUserRole, User user) + { + if (pendingUserRole.OrganisationUserGroupId == null) + { + var roleAlreadyExists = await _dataContext.UserAccessRole.FirstOrDefaultAsync(x => x.UserId == pendingUserRole.UserId && !x.IsDeleted && x.OrganisationEligibleRoleId == pendingUserRole.OrganisationEligibleRoleId); + + if (roleAlreadyExists == null) + { + user.UserAccessRoles.Add(new UserAccessRole { - emailList.Add(roleRequester.UserName); - } + UserId = user.Id, + OrganisationEligibleRoleId = pendingUserRole.OrganisationEligibleRoleId + }); + + // On role approval assign normal roles of service as well + await AssignOtherServiceGroupRoleToUser(serviceRoleGroupsWithApprovalRequiredRole, pendingUserRole, user); } + } + } - if (pendingUserRole.SendEmailNotification) + private async Task AssignOtherServiceGroupRoleToUser(List serviceRoleGroupsWithApprovalRequiredRole, UserAccessRolePending pendingUserRole, User user) + { + if (_appConfigInfo.ServiceRoleGroupSettings.Enable) + { + var serviceGroup = serviceRoleGroupsWithApprovalRequiredRole.FirstOrDefault(x => x.CcsServiceRoleMappings.Any(r => r.CcsAccessRoleId == pendingUserRole.OrganisationEligibleRole.CcsAccessRoleId)); + var serviceMappingCcsRoleIds = serviceGroup.CcsServiceRoleMappings.Where(y => y.CcsAccessRole.ApprovalRequired == 0).Select(x => x.CcsAccessRoleId).ToList(); + + var allOrgEligibleRoles = await _dataContext.OrganisationEligibleRole.Include(or => or.CcsAccessRole) + .Where(x => !x.IsDeleted && x.OrganisationId == pendingUserRole.OrganisationEligibleRole.OrganisationId && + serviceMappingCcsRoleIds.Contains(x.CcsAccessRoleId)).ToListAsync(); + foreach (var orgRole in allOrgEligibleRoles) { - foreach (var email in emailList) + if (!user.UserAccessRoles.Any(x => x.OrganisationEligibleRoleId == orgRole.Id && !x.IsDeleted)) { - if (status == UserPendingRoleStaus.Approved) - await _ccsSsoEmailService.SendRoleApprovedEmailAsync(email, user.UserName, serviceName, _appConfigInfo.ConclaveLoginUrl); - else - await _ccsSsoEmailService.SendRoleRejectedEmailAsync(email, user.UserName, serviceName); + user.UserAccessRoles.Add(new UserAccessRole + { + UserId = user.Id, + OrganisationEligibleRoleId = orgRole.Id + }); } } } - return await Task.FromResult(true); + } + private static void UpdateRoleRequestStatus(UserAccessRolePending pendingUserRole, UserPendingRoleStaus status) + { + pendingUserRole.Status = (int)status; + pendingUserRole.IsDeleted = true; } public async Task> GetUserRolesPendingForApprovalAsync(string userName) @@ -194,7 +253,8 @@ public async Task> GetUserRolesPendingForAppr var userAccessRolePendingAllList = await _dataContext.UserAccessRolePending .Include(u => u.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) - .Where(u => !u.IsDeleted && u.Status == (int)UserPendingRoleStaus.Pending && u.UserId == userId) + .Where(u => !u.IsDeleted && u.Status == (int)UserPendingRoleStaus.Pending + && u.OrganisationUserGroupId == null && u.UserId == userId) .ToListAsync(); var approvalRoleConfig = await _dataContext.RoleApprovalConfiguration.Where(x => !x.IsDeleted).ToListAsync(); @@ -212,7 +272,7 @@ public async Task> GetUserRolesPendingForAppr return userAccessRolePendingListResponse; } - public async Task RemoveApprovalPendingRolesAsync(string userName, string roleIds) + public async Task RemoveApprovalPendingRolesAsync(string userName, string roleIds, int? groupId = null) { if (!_appConfigInfo.UserRoleApproval.Enable) { @@ -236,8 +296,8 @@ public async Task RemoveApprovalPendingRolesAsync(string userName, string roleId throw new ResourceNotFoundException(); } - var userAccessRolePendingList = await _dataContext.UserAccessRolePending.Where(u => !u.IsDeleted && u.UserId == userId && - roles.Contains(u.OrganisationEligibleRoleId.ToString())).ToListAsync(); + var userAccessRolePendingList = await _dataContext.UserAccessRolePending.Where(u => !u.IsDeleted && u.UserId == userId + && u.OrganisationUserGroupId == groupId && roles.Contains(u.OrganisationEligibleRoleId.ToString())).ToListAsync(); if (userAccessRolePendingList.Any()) { @@ -302,6 +362,19 @@ public async Task VerifyAndReturnRoleApproval } else { + var status = isLinkExpired ? (int)UserPendingRoleStaus.Expired : userAccessRolePendingRoleDetails.Status; + + if (status != (int)UserPendingRoleStaus.Pending) + { + var isPendingRequest = await _dataContext.UserAccessRolePending + .AnyAsync(u => !u.IsDeleted && u.Status == (int)UserPendingRoleStaus.Pending + && u.Id != userAccessRolePendingRoleDetails.Id + && u.UserId == userAccessRolePendingRoleDetails.UserId + && u.OrganisationEligibleRoleId == userAccessRolePendingRoleDetails.OrganisationEligibleRoleId); + + status = isPendingRequest ? (int)UserPendingRoleStaus.Pending : status; + } + return new UserAccessRolePendingTokenDetails { Id = userAccessRolePendingRoleDetails.Id, @@ -309,7 +382,7 @@ public async Task VerifyAndReturnRoleApproval RoleId = userAccessRolePendingRoleDetails.OrganisationEligibleRole.CcsAccessRoleId, RoleName = userAccessRolePendingRoleDetails.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName, RoleKey = userAccessRolePendingRoleDetails.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey, - Status = isLinkExpired ? (int)UserPendingRoleStaus.Expired : userAccessRolePendingRoleDetails.Status + Status = status }; } } @@ -342,6 +415,7 @@ public async Task CreateUserRolesPendingForApprovalAsync(UserProfileEditRequestI var user = await _dataContext.User .Include(u => u.Party).ThenInclude(p => p.Person).ThenInclude(o => o.Organisation) + .Include(u => u.UserAccessRoles) .Include(u => u.UserAccessRolePending) .FirstOrDefaultAsync(u => !u.IsDeleted && u.UserName.ToLower() == userName.ToLower() && u.UserType == UserType.Primary); @@ -373,14 +447,6 @@ public async Task CreateUserRolesPendingForApprovalAsync(UserProfileEditRequestI throw new CcsSsoException(ErrorConstant.ErrorInvalidRoleInfo); } - var userAccessRoles = await _dataContext.UserAccessRole - .FirstOrDefaultAsync(uar => !uar.IsDeleted && uar.UserId == user.Id && roles.Contains(uar.OrganisationEligibleRoleId)); - - if (userAccessRoles != null) - { - throw new ResourceAlreadyExistsException("User Role already exists"); - } - var roleRequiredApprovalIds = await _dataContext.CcsAccessRole .Where(x => !x.IsDeleted && x.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalRequired) .Select(x => x.Id) @@ -395,51 +461,159 @@ public async Task CreateUserRolesPendingForApprovalAsync(UserProfileEditRequestI throw new CcsSsoException(ErrorConstant.ErrorInvalidRoleInfo); } - List userAccessRolePendingList = new List(); + var groupId = userProfileRequestInfo.Detail.GroupId; + + if (groupId != null) + { + var group = await _dataContext.OrganisationUserGroup + .Include(g => g.GroupEligibleRoles) + .Include(g => g.UserGroupMemberships).ThenInclude(ugm => ugm.User) + .FirstOrDefaultAsync(g => !g.IsDeleted && g.Id == groupId && g.Organisation.CiiOrganisationId == organisation.CiiOrganisationId); + + if (group == null) + { + throw new ResourceAlreadyExistsException(ErrorConstant.ErrorInvalidUserGroup); + } + + var groupEligibleRoleIds = group.GroupEligibleRoles.Where(x => !x.IsDeleted).Select(x => x.OrganisationEligibleRoleId); + if (!roles.All(roleId => groupEligibleRoleIds.Any(x => x == roleId))) + { + throw new CcsSsoException(ErrorConstant.ErrorInvalidUserGroupRole); + } + + var isUserMemberOfGroup = group.UserGroupMemberships.Where(x => !x.IsDeleted).Any(x => x.UserId == user.Id); + if (!isUserMemberOfGroup) + { + throw new CcsSsoException(ErrorConstant.ErrorInvalidUserInfo); + } + } + + // If GroupId is null then request is for user profile so we need check for user role exists + if (groupId == null) + { + var userAccessRoles = await _dataContext.UserAccessRole + .FirstOrDefaultAsync(uar => !uar.IsDeleted && uar.UserId == user.Id && roles.Contains(uar.OrganisationEligibleRoleId)); + + if (userAccessRoles != null) + { + throw new ResourceAlreadyExistsException(ErrorConstant.ErrorUserRoleAlreadyExists); + } + } + + List userAccessRolePendingToSendEmail = new List(); + List userAccessRolePendingAutoApproved = new List(); - List rolesToSendEmail = new List(); + var userAccessRoleIds = await _dataContext.UserAccessRole.Where(x => !x.IsDeleted && x.UserId == user.Id).Select(x => x.OrganisationEligibleRoleId).ToListAsync(); + var userGroupApprovedRoleIds = await GetUserGroupApprovedRoleIds(user, groupId); roles.ForEach((roleId) => { + var isUserAccessRoleRequestPending = user.UserAccessRolePending.Any(x => !x.IsDeleted + && x.OrganisationEligibleRoleId == Convert.ToInt32(roleId) + && x.Status == (int)UserPendingRoleStaus.Pending); + var isUserAccessRolePendingExist = user.UserAccessRolePending.Any(x => !x.IsDeleted && x.OrganisationEligibleRoleId == Convert.ToInt32(roleId) + && x.OrganisationUserGroupId == groupId && x.Status == (int)UserPendingRoleStaus.Pending); if (!isUserAccessRolePendingExist) { - user.UserAccessRolePending.Add(new UserAccessRolePending + var isRoleAssignedToUser = userAccessRoleIds.Any(x => x == roleId); + var isRoleAssignedToUserFromGroup = userGroupApprovedRoleIds.Any(x => x == roleId); + + var userAccessRolePending = new UserAccessRolePending { OrganisationEligibleRoleId = roleId, Status = (int)UserPendingRoleStaus.Pending, + OrganisationUserGroupId = groupId, SendEmailNotification = sendEmailNotification - }); - rolesToSendEmail.Add(roleId); + }; + + if (isRoleAssignedToUser || isRoleAssignedToUserFromGroup) + { + userAccessRolePending.Status = (int)UserPendingRoleStaus.Approved; + userAccessRolePending.IsDeleted = true; + userAccessRolePendingAutoApproved.Add(userAccessRolePending); + } + + user.UserAccessRolePending.Add(userAccessRolePending); + + if (!isUserAccessRoleRequestPending && userAccessRolePending.Status == (int)UserPendingRoleStaus.Pending) + { + userAccessRolePendingToSendEmail.Add(userAccessRolePending); + } } }); await _dataContext.SaveChangesAsync(); - if (rolesToSendEmail.Count > 0) + if (userAccessRolePendingToSendEmail.Count > 0) + { + await SendEmailForApprovalPendingRolesAsync(user, userAccessRolePendingToSendEmail); + } + + if (userAccessRolePendingAutoApproved.Count > 0) { - await SendEmailForApprovalPendingRolesAsync(user, rolesToSendEmail); + await AssignRoleToUserForAutoApproved(user, userAccessRolePendingAutoApproved); } } - private async Task SendEmailForApprovalPendingRolesAsync(User user, List roles) + private async Task AssignRoleToUserForAutoApproved(User user, List userAccessRolePendingAutoApproved) { - var userAccessRolePendingList = await _dataContext.UserAccessRolePending - .Include(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) - .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) - .Where(u => !u.IsDeleted && u.UserId == user.Id && u.Status == (int)UserPendingRoleStaus.Pending && roles.Contains(u.OrganisationEligibleRoleId)) - .ToListAsync(); + var serviceRoleGroupsWithApprovalRequiredRole = await _serviceRoleGroupMapperService.ServiceRoleGroupsWithApprovalRequiredRoleAsync(); + + foreach (UserAccessRolePending userAccessRolePending in userAccessRolePendingAutoApproved) + { + await AssignRequestedRoleToUser(serviceRoleGroupsWithApprovalRequiredRole, userAccessRolePending, user); + } + + await _dataContext.SaveChangesAsync(); + } + + private async Task> GetUserGroupApprovedRoleIds(User user, int? groupId) + { + List userGroupApprovedRoleIds = new List(); + + var userGroupIds = await _dataContext.UserGroupMembership.Where(x => !x.IsDeleted && x.UserId == user.Id).Select(x => x.OrganisationUserGroupId).ToListAsync(); + if (groupId != null) + { + userGroupIds = userGroupIds.Where(x => x != groupId).ToList(); + } + var userGroupRoleIds = await _dataContext.OrganisationGroupEligibleRole.Where(x => !x.IsDeleted && userGroupIds.Contains(x.OrganisationUserGroupId)).Select(x => x.OrganisationEligibleRoleId).ToListAsync(); + + userGroupRoleIds.ForEach((userGroupRoleId) => + { + // latest approved with in the group + var anyApprovedInTheGroup = user.UserAccessRolePending + .FirstOrDefault(x => x.OrganisationUserGroupId != null && x.OrganisationEligibleRoleId == userGroupRoleId && x.Status == (int)UserPendingRoleStaus.Approved); + + if (anyApprovedInTheGroup != null) + { + userGroupApprovedRoleIds.Add(userGroupRoleId); + } + }); + + return userGroupApprovedRoleIds; + } + private async Task SendEmailForApprovalPendingRolesAsync(User user, List userAccessRolePendingRequests) + { string orgName = user.Party.Person.Organisation.LegalName; - if (userAccessRolePendingList.Any()) + var roleApprovalConfigurations = await _dataContext.RoleApprovalConfiguration.ToListAsync(); + + foreach (var userAccessRolePendingRequest in userAccessRolePendingRequests) { - var roleApprovalConfigurations = await _dataContext.RoleApprovalConfiguration.ToListAsync(); + var userAccessRolePending = await _dataContext.UserAccessRolePending + .Include(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) + .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) + .FirstOrDefaultAsync(u => !u.IsDeleted && u.UserId == userAccessRolePendingRequest.UserId + && u.Status == userAccessRolePendingRequest.Status + && u.OrganisationUserGroupId == userAccessRolePendingRequest.OrganisationUserGroupId + && u.OrganisationEligibleRoleId == userAccessRolePendingRequest.OrganisationEligibleRoleId); - foreach (var userAccessRolePending in userAccessRolePendingList) + if (userAccessRolePending != null) { var roleApprovalConfiguration = roleApprovalConfigurations.FirstOrDefault(x => x.CcsAccessRoleId == userAccessRolePending.OrganisationEligibleRole.CcsAccessRoleId); @@ -465,7 +639,6 @@ private async Task SendEmailForApprovalPendingRolesAsync(User user, List ro } } } - public async Task> GetUserServiceRoleGroupsPendingForApprovalAsync(string userName) { if (!_appConfigInfo.ServiceRoleGroupSettings.Enable) diff --git a/api/CcsSso.Core.Service/External/UserProfileService.cs b/api/CcsSso.Core.Service/External/UserProfileService.cs index 89394f2a..df05164e 100644 --- a/api/CcsSso.Core.Service/External/UserProfileService.cs +++ b/api/CcsSso.Core.Service/External/UserProfileService.cs @@ -18,6 +18,7 @@ using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Threading.Tasks; @@ -82,11 +83,14 @@ public async Task CreateUserAsync(UserProfileEditRequestIn .Include(o => o.OrganisationEligibleRoles).ThenInclude(or => or.CcsAccessRole) .Include(o => o.OrganisationEligibleIdentityProviders) .FirstOrDefaultAsync(o => !o.IsDeleted && o.CiiOrganisationId == userProfileRequestInfo.OrganisationId); + if (organisation == null) { throw new ResourceNotFoundException(); } + var isUserDomainValid = userName?.ToLower().Split('@')?[1] == organisation.DomainName?.ToLower(); + var user = await _dataContext.User .FirstOrDefaultAsync(u => !u.IsDeleted && u.UserName == userName); @@ -130,8 +134,13 @@ public async Task CreateUserAsync(UserProfileEditRequestIn } } - // Set user groups + var groupsWithRoleRequiredApproval = new List>>(); + if (_appConfigInfo.UserRoleApproval.Enable && !isUserDomainValid && userProfileRequestInfo.Detail.GroupIds != null && userProfileRequestInfo.Detail.GroupIds.Any()) + { + groupsWithRoleRequiredApproval = GetGroupsWithApprovalOrgRole(organisation.UserGroups, userProfileRequestInfo.Detail.GroupIds); + } + var userGroupMemberships = new List(); userProfileRequestInfo.Detail.GroupIds?.ForEach((groupId) => { @@ -154,7 +163,6 @@ public async Task CreateUserAsync(UserProfileEditRequestIn { var ccsAccessRoleId = organisation.OrganisationEligibleRoles.FirstOrDefault(x => x.Id == roleId)?.CcsAccessRoleId; var isRoleRequiredApproval = ccsAccessRoleId != null && ccsAccessRoleRequiredApproval != null && ccsAccessRoleRequiredApproval.Any(x => x.Id == ccsAccessRoleId); - var isUserDomainValid = userName?.ToLower().Split('@')?[1] == organisation.DomainName?.ToLower(); if (_appConfigInfo.UserRoleApproval.Enable && !isUserDomainValid && isRoleRequiredApproval) { @@ -224,6 +232,23 @@ await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new }); } + if (groupsWithRoleRequiredApproval.Any()) + { + foreach (var group in groupsWithRoleRequiredApproval) + { + await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new UserProfileEditRequestInfo + { + UserName = userName, + OrganisationId = organisation.CiiOrganisationId, + Detail = new UserRequestDetail + { + GroupId = group.Key, + RoleIds = group.Value + }, + }, sendEmailNotification: false); + } + } + if (isConclaveConnectionIncluded) { SecurityApiUserInfo securityApiUserInfo = new SecurityApiUserInfo @@ -423,16 +448,30 @@ public async Task GetUserAsync(string userName, bool is if (user.UserGroupMemberships != null) { + var lastGroupRequest = await _dataContext.UserAccessRolePending.Include(u => u.User).OrderByDescending(x => x.Id).FirstOrDefaultAsync(x => x.User.UserName == userName.ToLower() + && x.OrganisationUserGroupId != null); + + List userGroupsApprovalServiceRoleGroups = new(); + if (lastGroupRequest != null && lastGroupRequest.Status != (int)UserPendingRoleStaus.Approved) + { + userGroupsApprovalServiceRoleGroups = await _serviceRoleGroupMapperService.OrgRolesToServiceRoleGroupsAsync(new List() { lastGroupRequest.OrganisationEligibleRoleId }); + } + foreach (var userGroupMembership in user.UserGroupMemberships) { if (!userGroupMembership.IsDeleted && userGroupMembership.OrganisationUserGroup.GroupEligibleRoles != null) { - - if (userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Any()) + if (userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Any(x => !x.IsDeleted)) { // For every role in the group populate the group role info foreach (var groupAccess in userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Where(x => !x.IsDeleted)) { + if (_appConfigInfo.UserRoleApproval.Enable && userGroupsApprovalServiceRoleGroups.Any(x => x.CcsServiceRoleMappings.Any(m => m.CcsAccessRoleId == groupAccess.OrganisationEligibleRole.CcsAccessRoleId))) + { + PopulateUserGroupWithoutRole(userProfileInfo, userGroupMembership); + continue; + } + var groupAccessRole = new GroupAccessRole { GroupId = userGroupMembership.OrganisationUserGroup.Id, @@ -448,15 +487,7 @@ public async Task GetUserAsync(string userName, bool is } else // If group doesnt have a role then just send the group with empty role { - var groupAccessRole = new GroupAccessRole - { - GroupId = userGroupMembership.OrganisationUserGroup.Id, - Group = userGroupMembership.OrganisationUserGroup.UserGroupName, - AccessRoleName = string.Empty, - AccessRole = string.Empty, - }; - - userProfileInfo.Detail.UserGroups.Add(groupAccessRole); + PopulateUserGroupWithoutRole(userProfileInfo, userGroupMembership); } } } @@ -467,6 +498,20 @@ public async Task GetUserAsync(string userName, bool is throw new ResourceNotFoundException(); } + + private static void PopulateUserGroupWithoutRole(UserProfileResponseInfo userProfileInfo, UserGroupMembership userGroupMembership) + { + var groupAccessRole = new GroupAccessRole + { + GroupId = userGroupMembership.OrganisationUserGroup.Id, + Group = userGroupMembership.OrganisationUserGroup.UserGroupName, + AccessRoleName = string.Empty, + AccessRole = string.Empty, + }; + + userProfileInfo.Detail.UserGroups.Add(groupAccessRole); + } + // #Delegated public async Task GetUsersV1Async(string organisationId, ResultSetCriteria resultSetCriteria, UserFilterCriteria userFilterCriteria) @@ -850,7 +895,7 @@ public async Task UpdateUserAsync(string userName, UserPro { throw new ResourceNotFoundException(); } - + var isUserDomainValid = userName?.ToLower().Split('@')?[1] == organisation.DomainName?.ToLower(); var user = await _dataContext.User .Include(u => u.Party).ThenInclude(p => p.Person) .Include(u => u.UserGroupMemberships) @@ -920,6 +965,9 @@ join cr in _dataContext.CcsAccessRole on er.CcsAccessRoleId equals cr.Id List requestRoles = new(); List previousIdentityProviderIds = new(); var userAccessRoleRequiredApproval = new List(); + var groupsWithRoleRequiredApproval = new List>>(); + var removedGroupIds = new List(); + var removedRoleIds = new List(); if (!isMyProfile || isAdminUser == true) { @@ -980,6 +1028,21 @@ join cr in _dataContext.CcsAccessRole on er.CcsAccessRoleId equals cr.Id hasProfileInfoChanged = true; } + // list of new groups to check for approval required role + if (userProfileRequestInfo.Detail.GroupIds != null) + { + var newlyAddedGroupIds = userProfileRequestInfo.Detail.GroupIds.Where(x => !previousGroups.Contains(x)).ToList(); + if (_appConfigInfo.UserRoleApproval.Enable && !isUserDomainValid && userProfileRequestInfo.Detail.GroupIds != null && newlyAddedGroupIds.Any()) + { + groupsWithRoleRequiredApproval = GetGroupsWithApprovalOrgRole(organisation.UserGroups, newlyAddedGroupIds); + } + } + + if (userProfileRequestInfo.Detail.GroupIds != null) + { + removedGroupIds = previousGroups.Where(x => !userProfileRequestInfo.Detail.GroupIds.Contains(x)).ToList(); + } + // Set groups var userGroupMemberships = new List(); userProfileRequestInfo.Detail.GroupIds?.ForEach((groupId) => @@ -1000,7 +1063,6 @@ join cr in _dataContext.CcsAccessRole on er.CcsAccessRoleId equals cr.Id { var ccsAccessRoleId = organisation.OrganisationEligibleRoles.FirstOrDefault(x => x.Id == roleId)?.CcsAccessRoleId; var isRoleRequiredApproval = ccsAccessRoleId != null && ccsAccessRoleRequiredApproval != null && ccsAccessRoleRequiredApproval.Any(x => x.Id == ccsAccessRoleId); - var isUserDomainValid = userName?.ToLower().Split('@')?[1] == organisation.DomainName?.ToLower(); if (_appConfigInfo.UserRoleApproval.Enable && !isUserDomainValid && isRoleRequiredApproval && !previousRoles.Any(x => x == roleId)) { @@ -1016,6 +1078,11 @@ join cr in _dataContext.CcsAccessRole on er.CcsAccessRoleId equals cr.Id }); user.UserAccessRoles = userAccessRoles; + if (userProfileRequestInfo.Detail.RoleIds != null) + { + removedRoleIds = previousRoles.Where(x => !userProfileRequestInfo.Detail.RoleIds.Contains(x)).ToList(); + } + // Check the admin group availability in request var noAdminRoleGroupInRequest = userProfileRequestInfo.Detail.GroupIds == null || !userProfileRequestInfo.Detail.GroupIds.Any() || !organisation.UserGroups.Any(g => !g.IsDeleted @@ -1107,7 +1174,11 @@ join cr in _dataContext.CcsAccessRole on er.CcsAccessRoleId equals cr.Id if (_appConfigInfo.UserRoleApproval.Enable) { + await RemoveRoleApproveRequest(removedRoleIds, user.Id); + await RemoveGroupRoleApproveRequest(removedGroupIds, user.Id); + await CreatePendingRoleRequest(userAccessRoleRequiredApproval, user.Id, userName, organisation.CiiOrganisationId); + await CreatePendingRoleRequestForGroup(groupsWithRoleRequiredApproval, user.Id, userName, organisation.CiiOrganisationId); } // Log @@ -1435,7 +1506,7 @@ private void Validate(UserProfileEditRequestInfo userProfileReqestInfo, bool isM if (userProfileReqestInfo.Detail.RoleIds != null && userProfileReqestInfo.Detail.RoleIds.Any(gId => !orgRoleIds.Contains(gId))) { throw new CcsSsoException(ErrorConstant.ErrorInvalidUserRole); - } + } if (userProfileReqestInfo.Detail.IdentityProviderIds == null || !userProfileReqestInfo.Detail.IdentityProviderIds.Any() || userProfileReqestInfo.Detail.IdentityProviderIds.Any(id => !orgIdpIds.Contains(id))) @@ -1859,11 +1930,11 @@ private async Task CreatePendingRoleRequest(List userAccessRoleRequiredAppr { // remove roles that were pending for approval but now no longer required var userAccessRoleRequiredToRemoveFromApproval = await _dataContext.UserAccessRolePending.Where(x => !x.IsDeleted && x.UserId == userId - && !userAccessRoleRequiredApproval.Contains(x.OrganisationEligibleRoleId) && x.Status == (int)UserPendingRoleStaus.Pending).ToListAsync(); + && !userAccessRoleRequiredApproval.Contains(x.OrganisationEligibleRoleId) && x.Status == (int)UserPendingRoleStaus.Pending && x.OrganisationUserGroupId == null).ToListAsync(); // get roles that are pending for approval var existingPendingRequestRole = await _dataContext.UserAccessRolePending.Where(x => !x.IsDeleted && x.UserId == userId - && userAccessRoleRequiredApproval.Contains(x.OrganisationEligibleRoleId) && x.Status == (int)UserPendingRoleStaus.Pending).ToListAsync(); + && userAccessRoleRequiredApproval.Contains(x.OrganisationEligibleRoleId) && x.Status == (int)UserPendingRoleStaus.Pending && x.OrganisationUserGroupId == null).ToListAsync(); // ignore roles which are still pending for approval foreach (var existingPendingRequestToIgnore in existingPendingRequestRole) @@ -1872,12 +1943,7 @@ private async Task CreatePendingRoleRequest(List userAccessRoleRequiredAppr } // Remove pending for approval role that are no longer required (not passed in request) - if (userAccessRoleRequiredToRemoveFromApproval != null && userAccessRoleRequiredToRemoveFromApproval.Any()) - { - var roleIds = userAccessRoleRequiredToRemoveFromApproval.Select(x => x.OrganisationEligibleRoleId).ToList(); - - await _userProfileRoleApprovalService.RemoveApprovalPendingRolesAsync(userName, string.Join(",", roleIds)); - } + userAccessRoleRequiredToRemoveFromApproval.ForEach(l => { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Removed; }); if (userAccessRoleRequiredApproval.Any()) { @@ -1891,7 +1957,32 @@ await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new } }); } + } + + private async Task RemoveGroupRoleApproveRequest(List groupIds, int userId) + { + if (groupIds != null && groupIds.Any()) + { + var userAccessRolePending = await _dataContext.UserAccessRolePending.Where(x => x.UserId == userId + && x.OrganisationUserGroupId != null && groupIds.Contains(x.OrganisationUserGroupId.Value)).ToListAsync(); + userAccessRolePending.ForEach(l => { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Removed; }); + + await _dataContext.SaveChangesAsync(); + } + } + + private async Task RemoveRoleApproveRequest(List roleIds, int userId) + { + if (roleIds != null && roleIds.Any()) + { + var userAccessRolePending = await _dataContext.UserAccessRolePending.Where(x => x.UserId == userId + && roleIds.Contains(x.OrganisationEligibleRoleId) && x.OrganisationUserGroupId == null).ToListAsync(); + + userAccessRolePending.ForEach(l => { l.IsDeleted = true; l.Status = (int)UserPendingRoleStaus.Removed; }); + + await _dataContext.SaveChangesAsync(); + } } #region User Profile Version 1 @@ -1932,6 +2023,15 @@ public async Task GetUserV1Async(string List groupAccessServiceRoleGroups = new List(); + var lastGroupRequest = await _dataContext.UserAccessRolePending.Include(u => u.User).OrderByDescending(x => x.Id).FirstOrDefaultAsync(x => x.User.UserName == userName.ToLower() + && x.OrganisationUserGroupId != null); + + List userGroupsApprovalServiceRoleGroups = new(); + if (lastGroupRequest != null && lastGroupRequest.Status != (int)UserPendingRoleStaus.Approved) + { + userGroupsApprovalServiceRoleGroups = await _serviceRoleGroupMapperService.OrgRolesToServiceRoleGroupsAsync(new List() { lastGroupRequest.OrganisationEligibleRoleId }); + } + foreach (var groupId in groupIds) { var groupInfo = await _organisationGroupService.GetServiceRoleGroupAsync(userProfileResponseInfo.OrganisationId, groupId); @@ -1940,6 +2040,15 @@ public async Task GetUserV1Async(string { foreach (var serviceRoleGroup in groupInfo.ServiceRoleGroups) { + if (_appConfigInfo.UserRoleApproval.Enable && userGroupsApprovalServiceRoleGroups.Any(x => x.Id == serviceRoleGroup.Id)) + { + groupAccessServiceRoleGroups.Add(new GroupAccessServiceRoleGroup() + { + GroupId = groupInfo.GroupId, + Group = groupInfo.GroupName, + }); + continue; + } groupAccessServiceRoleGroups.Add(new GroupAccessServiceRoleGroup() { GroupId = groupInfo.GroupId, @@ -2002,6 +2111,10 @@ public async Task UpdateUserV1Async(string userName, UserP var serviceRoleGroupIds = userProfileServiceRoleGroupEditRequestInfo?.Detail?.ServiceRoleGroupIds; var organisationId = userProfileServiceRoleGroupEditRequestInfo?.OrganisationId; + + userName = userName?.ToLower(); + userProfileServiceRoleGroupEditRequestInfo.UserName = userProfileServiceRoleGroupEditRequestInfo.UserName?.ToLower(); + _userHelper.ValidateUserName(userName); if (userName != userProfileServiceRoleGroupEditRequestInfo.UserName) @@ -2207,5 +2320,60 @@ private async Task ValidateJoiningRequestAsync(Dictionary>> GetGroupsWithApprovalOrgRole(List userGroups, List groupIds) + { + var grpWithPendingOrgEligibleRole = new List>>(); + var allSelectedGroups = userGroups.Where(oug => groupIds.Contains(oug.Id) && !oug.IsDeleted).ToList(); + + foreach (var group in allSelectedGroups) + { + List approvalRequiredRoles = new(); + foreach (var role in group.GroupEligibleRoles.Where(x => !x.IsDeleted)) + { + if (role.OrganisationEligibleRole.CcsAccessRole.ApprovalRequired == (int)RoleApprovalRequiredStatus.ApprovalRequired) + { + approvalRequiredRoles.Add(role.OrganisationEligibleRoleId); + } + } + if (approvalRequiredRoles.Any()) + { + grpWithPendingOrgEligibleRole.Add(new KeyValuePair>(group.Id, approvalRequiredRoles)); + } + } + + return grpWithPendingOrgEligibleRole; + } + + private async Task CreatePendingRoleRequestForGroup(List>> groupsWithRoleRequiredApproval, int userId, string userName, string ciiOrganisationId) + { + var userPendingGroups = await _dataContext.UserAccessRolePending.Where(x => !x.IsDeleted && x.UserId == userId && x.Status == (int)UserPendingRoleStaus.Pending && x.OrganisationUserGroupId != null).ToListAsync(); + + foreach (var group in groupsWithRoleRequiredApproval) + { + var existingPendingRequestRole = userPendingGroups.Where(x => !x.IsDeleted && x.OrganisationUserGroupId == group.Key && group.Value.Contains(x.OrganisationEligibleRoleId)).ToList(); + + // ignore roles which are still pending for approval + foreach (var existingPendingRequestToIgnore in existingPendingRequestRole) + { + group.Value.Remove(existingPendingRequestToIgnore.OrganisationEligibleRoleId); + } + + if (group.Value.Any()) + { + await _userProfileRoleApprovalService.CreateUserRolesPendingForApprovalAsync(new UserProfileEditRequestInfo + { + UserName = userName, + OrganisationId = ciiOrganisationId, + Detail = new UserRequestDetail + { + GroupId = group.Key, + RoleIds = group.Value + } + }, sendEmailNotification: false); + } + } + + } } } \ No newline at end of file diff --git a/api/CcsSso.Core.Service/UserService.cs b/api/CcsSso.Core.Service/UserService.cs index d2b968ef..10fd383c 100644 --- a/api/CcsSso.Core.Service/UserService.cs +++ b/api/CcsSso.Core.Service/UserService.cs @@ -1,3 +1,4 @@ +using CcsSso.Core.DbModel.Constants; using CcsSso.Core.Domain.Contracts; using CcsSso.Core.Domain.Contracts.External; using CcsSso.DbModel.Entity; @@ -12,105 +13,132 @@ namespace CcsSso.Service { - public class UserService : IUserService + public class UserService : IUserService + { + private readonly IDataContext _dataContext; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ApplicationConfigurationInfo _applicationConfigurationInfo; + private readonly ICcsSsoEmailService _ccsSsoEmailService; + private readonly IUserProfileHelperService _userHelper; + private readonly IServiceRoleGroupMapperService _serviceRoleGroupMapperService; + + public UserService(IDataContext dataContext, IHttpClientFactory httpClientFactory, + ApplicationConfigurationInfo applicationConfigurationInfo, ICcsSsoEmailService ccsSsoEmailService, IUserProfileHelperService userHelper, IServiceRoleGroupMapperService serviceRoleGroupMapperService) { - private readonly IDataContext _dataContext; - private readonly IHttpClientFactory _httpClientFactory; - private readonly ApplicationConfigurationInfo _applicationConfigurationInfo; - private readonly ICcsSsoEmailService _ccsSsoEmailService; - private readonly IUserProfileHelperService _userHelper; - - public UserService(IDataContext dataContext, IHttpClientFactory httpClientFactory, - ApplicationConfigurationInfo applicationConfigurationInfo, ICcsSsoEmailService ccsSsoEmailService, IUserProfileHelperService userHelper) - { - _dataContext = dataContext; - _httpClientFactory = httpClientFactory; - _applicationConfigurationInfo = applicationConfigurationInfo; - _ccsSsoEmailService = ccsSsoEmailService; - _userHelper = userHelper; - } + _dataContext = dataContext; + _httpClientFactory = httpClientFactory; + _applicationConfigurationInfo = applicationConfigurationInfo; + _ccsSsoEmailService = ccsSsoEmailService; + _userHelper = userHelper; + _serviceRoleGroupMapperService = serviceRoleGroupMapperService; + } - public async Task> GetPermissions(string userName, string serviceClientId, string organisationId) - { - var users = await _dataContext.User.Include(p => p.Party).ThenInclude(p => p.Person).ThenInclude(o => o.Organisation) - .Include(u => u.UserGroupMemberships).ThenInclude(ugm => ugm.OrganisationUserGroup) - .ThenInclude(oug => oug.GroupEligibleRoles).ThenInclude(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) - .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) - .Include(u => u.UserAccessRoles).ThenInclude(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) - .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) - .Where(u => !u.IsDeleted && u.UserName == userName).ToListAsync(); - - User user; - if (!string.IsNullOrWhiteSpace(organisationId)) - { - user = users.FirstOrDefault(u => u.Party.Person.Organisation.CiiOrganisationId == organisationId); - } - else - { - user = users.SingleOrDefault(u => u.UserType == Core.DbModel.Constants.UserType.Primary); - } + public async Task> GetPermissions(string userName, string serviceClientId, string organisationId) + { + var users = await _dataContext.User.Include(p => p.Party).ThenInclude(p => p.Person).ThenInclude(o => o.Organisation) + .Include(u => u.UserGroupMemberships).ThenInclude(ugm => ugm.OrganisationUserGroup) + .ThenInclude(oug => oug.GroupEligibleRoles).ThenInclude(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) + .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) + .Include(u => u.UserAccessRoles).ThenInclude(gr => gr.OrganisationEligibleRole).ThenInclude(or => or.CcsAccessRole) + .ThenInclude(or => or.ServiceRolePermissions).ThenInclude(sr => sr.ServicePermission).ThenInclude(sr => sr.CcsService) + .Where(u => !u.IsDeleted && u.UserName == userName).ToListAsync(); - var rolePermissions = user.UserAccessRoles.Where(uar => !uar.IsDeleted).Select(uar => new UserRolePermissionInfo - { - RoleKey = uar.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey, - RoleName = uar.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName, - PermissionList = uar.OrganisationEligibleRole.CcsAccessRole.ServiceRolePermissions.Where(sp => sp.ServicePermission.CcsService.ServiceClientId == serviceClientId).Select(srp => srp.ServicePermission.ServicePermissionName).ToList() - }).ToList(); + User user; + if (!string.IsNullOrWhiteSpace(organisationId)) + { + user = users.FirstOrDefault(u => u.Party.Person.Organisation.CiiOrganisationId == organisationId); + } + else + { + user = users.SingleOrDefault(u => u.UserType == Core.DbModel.Constants.UserType.Primary); + } - if (user.UserGroupMemberships != null) - { - foreach (var userGroupMembership in user.UserGroupMemberships) - { - if (!userGroupMembership.IsDeleted && userGroupMembership.OrganisationUserGroup.GroupEligibleRoles != null) - { - - if (userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Any()) - { - foreach (var groupAccess in userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Where(x => !x.IsDeleted)) - { - var groupAccessRole = new UserRolePermissionInfo - { - RoleName = groupAccess.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName, - RoleKey = groupAccess.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey, - PermissionList = groupAccess.OrganisationEligibleRole.CcsAccessRole.ServiceRolePermissions.Where(sp => sp.ServicePermission.CcsService.ServiceClientId == serviceClientId).Select(srp => srp.ServicePermission.ServicePermissionName).ToList() - }; - rolePermissions.Add(groupAccessRole); - } - } - } - } - } + var rolePermissions = user.UserAccessRoles.Where(uar => !uar.IsDeleted).Select(uar => new UserRolePermissionInfo + { + RoleKey = uar.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey, + RoleName = uar.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName, + PermissionList = uar.OrganisationEligibleRole.CcsAccessRole.ServiceRolePermissions.Where(sp => sp.ServicePermission.CcsService.ServiceClientId == serviceClientId).Select(srp => srp.ServicePermission.ServicePermissionName).ToList() + }).ToList(); - var permissions = rolePermissions.SelectMany(rp => rp.PermissionList, (r, p) => new ServicePermissionDto() - { - RoleKey = r.RoleKey, - RoleName = r.RoleName, - PermissionName = p - }).Distinct().ToList(); + if (user.UserGroupMemberships != null) + { + await GetGroupPermissions(serviceClientId, user, rolePermissions); + } - return permissions; - } + var permissions = rolePermissions.SelectMany(rp => rp.PermissionList, (r, p) => new ServicePermissionDto() + { + RoleKey = r.RoleKey, + RoleName = r.RoleName, + PermissionName = p + }).Distinct().ToList(); - public async Task SendUserActivationEmailAsync(string email, bool isExpired = false) - { - _userHelper.ValidateUserName(email); + return permissions; + } - var client = _httpClientFactory.CreateClient("default"); - client.BaseAddress = new Uri(_applicationConfigurationInfo.SecurityApiDetails.Url); - var url = $"security/users/activation-emails?is-expired={isExpired}"; - client.DefaultRequestHeaders.Add("X-API-Key", _applicationConfigurationInfo.SecurityApiDetails.ApiKey); + public async Task SendUserActivationEmailAsync(string email, bool isExpired = false) + { + _userHelper.ValidateUserName(email); - var list = new List>(); - list.Add(new KeyValuePair("email", email)); - HttpContent codeContent = new FormUrlEncodedContent(list); - await client.PostAsync(url, codeContent); + var client = _httpClientFactory.CreateClient("default"); + client.BaseAddress = new Uri(_applicationConfigurationInfo.SecurityApiDetails.Url); + var url = $"security/users/activation-emails?is-expired={isExpired}"; + client.DefaultRequestHeaders.Add("X-API-Key", _applicationConfigurationInfo.SecurityApiDetails.ApiKey); + + var list = new List>(); + list.Add(new KeyValuePair("email", email)); + HttpContent codeContent = new FormUrlEncodedContent(list); + await client.PostAsync(url, codeContent); + } + + public async Task NominateUserAsync(string email) + { + _userHelper.ValidateUserName(email); + var url = _applicationConfigurationInfo.ConclaveSettings.BaseUrl + _applicationConfigurationInfo.ConclaveSettings.OrgRegistrationRoute; + await _ccsSsoEmailService.SendNominateEmailAsync(email, url); + } + + private async Task GetGroupPermissions(string serviceClientId, User user, List rolePermissions) + { + var userGroupsApprovalRequest = await _dataContext.UserAccessRolePending.Where(x => x.UserId == user.Id && x.OrganisationUserGroupId != null).ToListAsync(); + + foreach (var userGroupMembership in user.UserGroupMemberships) + { + if (!userGroupMembership.IsDeleted && userGroupMembership.OrganisationUserGroup.GroupEligibleRoles != null && userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Any()) + { + await AddRolePermissionsFromGroup(serviceClientId, rolePermissions, userGroupsApprovalRequest, userGroupMembership); } + } + } - public async Task NominateUserAsync(string email) + private async Task AddRolePermissionsFromGroup(string serviceClientId, List rolePermissions, List userGroupsApprovalRequest, UserGroupMembership userGroupMembership) + { + foreach (var groupAccess in userGroupMembership.OrganisationUserGroup.GroupEligibleRoles.Where(x => !x.IsDeleted)) + { + if (userGroupsApprovalRequest.Any()) { - _userHelper.ValidateUserName(email); - var url = _applicationConfigurationInfo.ConclaveSettings.BaseUrl + _applicationConfigurationInfo.ConclaveSettings.OrgRegistrationRoute; - await _ccsSsoEmailService.SendNominateEmailAsync(email, url); + var lastGroupRequest = userGroupsApprovalRequest.OrderByDescending(x => x.Id).FirstOrDefault(x => x.OrganisationUserGroupId == groupAccess.OrganisationUserGroupId); + if (lastGroupRequest != null && lastGroupRequest.Status != (int)UserPendingRoleStaus.Approved) + { + var serviceRoleGroups = await _serviceRoleGroupMapperService.OrgRolesToServiceRoleGroupsAsync(new List() { lastGroupRequest.OrganisationEligibleRoleId }); + + if (_applicationConfigurationInfo.UserRoleApproval.Enable && serviceRoleGroups.Any(g => g.CcsServiceRoleMappings.Any(m => m.CcsAccessRoleId == groupAccess.OrganisationEligibleRole.CcsAccessRoleId))) + { + continue; + } + } } + rolePermissions.Add(GetGroupRolePermissions(serviceClientId, groupAccess)); + } + } + + private static UserRolePermissionInfo GetGroupRolePermissions(string serviceClientId, Core.DbModel.Entity.OrganisationGroupEligibleRole groupAccess) + { + return new UserRolePermissionInfo + { + RoleName = groupAccess.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleName, + RoleKey = groupAccess.OrganisationEligibleRole.CcsAccessRole.CcsAccessRoleNameKey, + PermissionList = groupAccess.OrganisationEligibleRole.CcsAccessRole.ServiceRolePermissions.Where(sp => sp.ServicePermission.CcsService.ServiceClientId == serviceClientId).Select(srp => srp.ServicePermission.ServicePermissionName).ToList() + }; } + } } diff --git a/api/CcsSso.Core.Tests/External/OrganisationGroupServiceTest.cs b/api/CcsSso.Core.Tests/External/OrganisationGroupServiceTest.cs index f67d1e73..f2fbb82b 100644 --- a/api/CcsSso.Core.Tests/External/OrganisationGroupServiceTest.cs +++ b/api/CcsSso.Core.Tests/External/OrganisationGroupServiceTest.cs @@ -10,6 +10,7 @@ using CcsSso.Domain.Contracts; using CcsSso.Domain.Dtos; using CcsSso.Domain.Exceptions; +using CcsSso.Shared.Cache.Contracts; using CcsSso.Shared.Domain.Contexts; using Microsoft.EntityFrameworkCore; using Moq; @@ -972,9 +973,12 @@ public static OrganisationGroupService UserService(IDataContext dataContext) ApplicationConfigurationInfo applicationConfigurationInfo = new(); var mockRolesToServiceRoleGroupMapperService = new Mock(); var mockOrganisationProfileService = new Mock(); + var mockUserProfileRoleApprovalService = new Mock(); + var localCacheService = new Mock(); var service = new OrganisationGroupService(dataContext, userProfileHelperService, mockAuditLoginService.Object, mockEmailService.Object, - mockCacheService.Object, applicationConfigurationInfo, mockRolesToServiceRoleGroupMapperService.Object, mockOrganisationProfileService.Object); + mockCacheService.Object, applicationConfigurationInfo, mockRolesToServiceRoleGroupMapperService.Object, mockOrganisationProfileService.Object, mockUserProfileRoleApprovalService.Object + , localCacheService.Object); return service; } diff --git a/api/CcsSso.Core.Tests/UserServiceTests.cs b/api/CcsSso.Core.Tests/UserServiceTests.cs index 969571fa..c9ca3672 100644 --- a/api/CcsSso.Core.Tests/UserServiceTests.cs +++ b/api/CcsSso.Core.Tests/UserServiceTests.cs @@ -56,6 +56,7 @@ static async Task GetUserService(IDataContext dataContext) var mockHttpClientFactory = new Mock(); var mockCcsSsoEmailService = new Mock(); var mockUserHelperService = new Mock(); + var mockServiceRoleGroupMapperService = new Mock(); var applicationConfigurationInfo = new ApplicationConfigurationInfo { EnableAdapterNotifications = false, @@ -65,7 +66,7 @@ static async Task GetUserService(IDataContext dataContext) } }; await SetupTestDataAsync(dataContext); - return new UserService(dataContext, mockHttpClientFactory.Object, applicationConfigurationInfo, mockCcsSsoEmailService.Object, mockUserHelperService.Object); + return new UserService(dataContext, mockHttpClientFactory.Object, applicationConfigurationInfo, mockCcsSsoEmailService.Object, mockUserHelperService.Object, mockServiceRoleGroupMapperService.Object); } static async Task SetupTestDataAsync(IDataContext dataContext) diff --git a/api/CcsSso.sln b/api/CcsSso.sln index 132137bd..3d89c8eb 100644 --- a/api/CcsSso.sln +++ b/api/CcsSso.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32014.148 @@ -80,6 +79,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CcsSso.Core.ServiceOnboardi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CcsSso.Core.BSIRolesRemovalOneTimeJob", "BSIRolesRemovalOneTimeJob\CcsSso.Core.BSIRolesRemovalOneTimeJob.csproj", "{219802C2-39AF-4890-964A-D9FE976FDD97}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CcsSso.Core.PPONScheduler", "CcsSso.Core.PPONScheduler\CcsSso.Core.PPONScheduler.csproj", "{B09C54AB-9462-40D0-8F54-6F0EBD719B7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -206,6 +207,10 @@ Global {219802C2-39AF-4890-964A-D9FE976FDD97}.Debug|Any CPU.Build.0 = Debug|Any CPU {219802C2-39AF-4890-964A-D9FE976FDD97}.Release|Any CPU.ActiveCfg = Release|Any CPU {219802C2-39AF-4890-964A-D9FE976FDD97}.Release|Any CPU.Build.0 = Release|Any CPU + {B09C54AB-9462-40D0-8F54-6F0EBD719B7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B09C54AB-9462-40D0-8F54-6F0EBD719B7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B09C54AB-9462-40D0-8F54-6F0EBD719B7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B09C54AB-9462-40D0-8F54-6F0EBD719B7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,8 +246,9 @@ Global {334BDF91-AA22-4583-B7D4-560876ACDA36} = {179F7A96-5DE5-47E9-BC61-F5E4276EF60C} {EBC3F606-77A2-4E94-AC2B-060C3A5CE1E7} = {179F7A96-5DE5-47E9-BC61-F5E4276EF60C} {219802C2-39AF-4890-964A-D9FE976FDD97} = {179F7A96-5DE5-47E9-BC61-F5E4276EF60C} + {B09C54AB-9462-40D0-8F54-6F0EBD719B7E} = {179F7A96-5DE5-47E9-BC61-F5E4276EF60C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {37B13717-0A0F-4D37-A3DA-BA346AE5D064} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal