/// <summary> /// Clears the EF connections pool cache /// </summary> protected void EfConnectionsPoolCacheCleanup() { ConsoleEx.Write("EF connections pool cache cleanup... ", ConsoleColor.DarkYellow); //EF caches connections, so in some scenarios, when there was a conn to a db that is being dropped and recreated EF will fail to //reconnect to it. //After such fail EF will reset the conns pool cache and open up new connections //So basically here just force the EF conn failure so it can drop previously cachec connections that are not valid anymore try { var ctx = new MapHiveDbContext(); // ReSharper disable once ReturnValueOfPureMethodIsNotUsed ctx.Applications.FirstOrDefault(); } catch (Exception) { //ignore } try { var userAccountService = CustomUserAccountService.GetInstance("MapHiveMbr"); userAccountService.GetByEmail("*****@*****.**"); } catch (Exception) { //ignore } ConsoleEx.Write("Done!" + Environment.NewLine, ConsoleColor.DarkGreen); }
/// <summary> /// Reads organization's licence options /// </summary> /// <param name="org"></param> /// <param name="dbCtx"></param> /// <returns></returns> public static async Task ReadLicenseOptionsAsync(this Organization org, MapHiveDbContext dbCtx) { //FIXME - see TODO below; this will need to be optimised! //apps org.LicenseOptions.Apply((await org.GetOrganizationAssetsAsync <Application>(dbCtx))?.assets); }
/// <summary> /// Renames a database; will throw if a name is not valid /// </summary> /// <param name="mhDbCtx"></param> /// <param name="newName"></param> /// <returns></returns> public async Task RenameAsync(MapHiveDbContext mhDbCtx, string newName) { //first cut the connection to the db if any DisconnectDatabase(); //rename the db using (var conn = new NpgsqlConnection(GetConnectionString(true))) { conn.Open(); var cmd = new NpgsqlCommand { Connection = conn, CommandText = $"ALTER DATABASE {DbName} RENAME TO {newName}" }; cmd.ExecuteNonQuery(); conn.Close(); conn.Dispose(); } //and update self DbName = newName; await UpdateAsync <OrganizationDatabase>(mhDbCtx, Uuid); }
/// <summary> /// Produces a maphive db context with db speciofied via dsc /// </summary> /// <returns></returns> protected MapHiveDbContext GetMapHiveDbContext() { using (var dbCtx = new MapHiveDbContext()) { return((MapHiveDbContext)dbCtx.ProduceDbContextInstance(Dsc.GetConnectionString(), true, DataSourceProvider.Npgsql)); } }
public async Task <IHttpActionResult> ActivateAccount(AccountActivationInput activationInput, string appCtx = null) { try { var activationOutput = await Auth.ActivateAccountAsync(CustomUserAccountService.GetInstance("MapHiveMbr"), activationInput.VerificationKey, activationInput.InitialPassword); //need to resend email with new verification key, as the previous one was stale if (activationOutput.VerificationKeyStale) { var dbCtx = new MapHiveDbContext("MapHiveMeta"); var emailStuff = await GetEmailStuffAsync("activate_account_stale", appCtx, dbCtx); //basically need to send an email the verification key has expired and send a new one var user = await dbCtx.Users.Where(u => u.Email == activationOutput.Email).FirstOrDefaultAsync(); //since got an email off mbr, user should not be null, but just in a case... if (user == null) { return(BadRequest()); } //prepare the email template tokens var tokens = new Dictionary <string, object> { { "UserName", $"{user.GetFullUserName()} ({user.Email})" }, { "Email", user.Email }, { "RedirectUrl", this.GetRequestSource().Split('#')[0] }, { "VerificationKey", activationOutput.VerificationKey }, { "InitialPassword", "" } }; //prepare and send the email EmailSender.Send(emailStuff.Item1, emailStuff.Item2.Prepare(tokens), user.Email); } //mbr has not found a user, so bad, bad, bad request it was if (activationOutput.UnknownUser) { return(BadRequest()); } //wipe out some potentially sensitive data activationOutput.Email = null; activationOutput.VerificationKey = null; return(Ok(activationOutput)); } catch (Exception ex) { return(HandleException(ex)); } }
public static void SeedTestLinks(MapHiveDbContext context) { #if DEBUG try { var l = new Link { ParentTypeUuid = default(Guid), ChildTypeUuid = default(Guid), ParentUuid = default(Guid), ChildUuid = default(Guid) }; l.LinkData.Add("some_link_data_consumer", new Dictionary <string, object> { { "prop1", "Some textual property" }, { "prop2", 123 }, { "prop3", DateTime.Now } }); context.Links.AddOrUpdate( l, new Link { ParentTypeUuid = default(Guid), ChildTypeUuid = default(Guid), ParentUuid = default(Guid), ChildUuid = default(Guid), LinkData = new LinkData { { "will_this_nicely_serialize", new Dictionary <string, object> { { "prop1", new Application() }, { "prop2", new Link() } } } } } ); } catch (Exception ex) { try { System.IO.File.WriteAllText(@"f:\err.txt", ex.Message + Environment.NewLine + ex.StackTrace); } catch { //ignore } } #endif }
public static void SeedAll(MapHiveDbContext context) { var mi = typeof(Seed).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); foreach (var m in mi) { if (m.Name != nameof(SeedAll) && m.Name.StartsWith("Seed")) { //assume there is one param for the time being //may have to change it later m.Invoke(null, new object[] { context }); } } }
public static void SeedLangs(MapHiveDbContext context) { context.Langs.AddOrUpdate(new Lang { Uuid = Guid.Parse("ece753c3-f079-4772-8aa2-0960aeabc94d"), LangCode = "pl", Name = "Polski" }, new Lang { Uuid = Guid.Parse("8323d1bb-e6f5-49d3-a441-837017d6e97e"), LangCode = "en", Name = "English", IsDefault = true }); }
/// <summary> /// Provides organisation context for a request /// </summary> /// <param name="actionContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) { //extracting data only if key present in the action params will let controllers flagged with org ctx attribute to work without the org ctx if needed //org ctx getters will trhow though if one tries to obtain the ctx in such scenario if (actionContext.ActionArguments.ContainsKey(OrgIdPropertyName)) { var orgId = (Guid)actionContext.ActionArguments[OrgIdPropertyName]; actionContext.Request.Properties.Add(OrgIdPropertyName, orgId); using (var dbCtx = new MapHiveDbContext()) { actionContext.Request.Properties.Add(OrgCtxPropertyName, await dbCtx.Organisations.FirstOrDefaultAsync(o => o.Uuid == orgId, cancellationToken)); } await base.OnActionExecutingAsync(actionContext, cancellationToken); } }
/// <summary> /// checks if an org can access an application /// </summary> /// <param name="dbCtx"></param> /// <param name="orgId"></param> /// <param name="appId"></param> /// <returns></returns> public static async Task <bool> CanUseApp(MapHiveDbContext dbCtx, Guid orgId, Guid appId) { var app = await dbCtx.Applications.FirstOrDefaultAsync(a => a.Uuid == appId); if (app == null) { return(false); } //if an ap is common then every organisation can access it if (app.IsCommon) { return(true); } //app is not common, so need to see if an app is assigned to this very org return(await app.HasParentLinkAsync(dbCtx, orgId)); }
public async Task <IHttpActionResult> PassResetRequest(PassResetRequestInput input, string appCtx = null) { //Note: basically this is a pass reset request, so NO need to inform a potential attacker about exceptions - always return ok! try { var requestPassResetOutput = await Auth.RequestPassResetAsync(CustomUserAccountService.GetInstance("MapHiveMbr"), input.Email); var dbCtx = new MapHiveDbContext("MapHiveMeta"); var emailStuff = await GetEmailStuffAsync("pass_reset_request", appCtx, dbCtx); //basically need to send an email the verification key has expired and send a new one var user = await dbCtx.Users.Where(u => u.Email == input.Email).FirstOrDefaultAsync(); //since got here and email off mbr, user should not be null, but just in a case... if (user == null) { //return BadRequest(); return(Ok()); } //prepare the email template tokens var tokens = new Dictionary <string, object> { { "UserName", $"{user.GetFullUserName()} ({user.Email})" }, { "Email", user.Email }, { "RedirectUrl", this.GetRequestSource().Split('#')[0] }, { "VerificationKey", requestPassResetOutput.VerificationKey } }; //prepare and send the email EmailSender.Send(emailStuff.Item1, emailStuff.Item2.Prepare(tokens), user.Email); return(Ok()); } catch (Exception ex) { //return HandleException(ex); return(Ok()); } }
/// <summary> /// Handles adding default platform applications /// </summary> /// <param name="args"></param> protected virtual async Task Handle_AddDefaultApps(IDictionary <string, string> args) { var cmd = GetCallerName(); if (GetHelp(args)) { Console.WriteLine( $"'{cmd}' : adds default apps to."); Console.WriteLine($"syntax: {cmd}"); Console.WriteLine(); return; } ConsoleEx.Write("Registering default platform apps...", ConsoleColor.DarkYellow); var ctx = new MapHiveDbContext(); ctx.Applications.AddOrUpdate(GetApps()); await ctx.SaveChangesAsync(); ConsoleEx.WriteOk("Done!"); }
/// <summary> /// Creates an org owner account - creates a user profile, an organization for a user, ties all the bits and pieces together /// </summary> /// <param name="dbCtx"></param> /// <param name="input"></param> /// <param name="emailSender"></param> /// <returns></returns> public static async Task <AccountCreateOutput> CreateAccountAsync( MapHiveDbContext dbCtx, AccountCreateInput input, IEmailSender emailSender ) { var user = new MapHive.Core.DataModel.MapHiveUser() { Email = input.AccountDetails.Email, Slug = input.AccountDetails.Slug, Forename = input.AccountDetails.Forename, Surname = input.AccountDetails.Surname, Company = input.AccountDetails.Company, Department = input.AccountDetails.Department, ContactPhone = input.AccountDetails.ContactPhone }; //now the org object var orgNameDesc = string.IsNullOrEmpty(input.AccountDetails.Company) ? input.AccountDetails.Email + "-org" : input.AccountDetails.Company; var newOrg = new Organization { DisplayName = orgNameDesc, Description = orgNameDesc, Slug = Utils.Slug.GetOrgSlug(orgNameDesc, user.Slug + "-org"), //push to extra billing info? BillingExtraInfo = new SerializableDictionaryOfString { { nameof(input.AccountDetails.ContactPhone), input.AccountDetails.ContactPhone }, { nameof(input.AccountDetails.Email), input.AccountDetails.Email }, { "ContactPerson", $"{input.AccountDetails.Forename} {input.AccountDetails.Surname}" }, { nameof(input.AccountDetails.Street), input.AccountDetails.Street }, { nameof(input.AccountDetails.HouseNo), input.AccountDetails.HouseNo }, { nameof(input.AccountDetails.FlatNo), input.AccountDetails.FlatNo }, { nameof(input.AccountDetails.Postcode), input.AccountDetails.Postcode }, { nameof(input.AccountDetails.City), input.AccountDetails.City }, { nameof(input.AccountDetails.Country), input.AccountDetails.Country }, { nameof(input.AccountDetails.VatNumber), input.AccountDetails.VatNumber } }, LicenseOptions = new OrganizationLicenseOptions() }; //got the user and org, o need to validate them now in order to ensure they are creatable if (user.Slug == newOrg.Slug) { var ex = new ValidationFailedException(); ex.ValidationErrors.Add(new ValidationError { Message = $"MapHiveUser slug already taken: {user.Slug}", Code = "user_org_slug_duplicate", PropertyName = nameof(MapHiveUser.Slug) }); throw ex; } //validate user and org - both should throw if a slug is already taken await user.ValidateAsync(dbCtx); await newOrg.ValidateAsync(dbCtx); //prepare the email template tokens known at this stage, var tokens = new Dictionary <string, object> { { "UserName", $"{user.GetFullUserName()} ({user.Email})" }, { "Email", user.Email } }; //and create user var accountCreateOutput = await MapHive.Core.DataModel.MapHiveUser.CreateUserAccountAsync(dbCtx, user, emailSender, input.EmailAccount, input.EmailTemplate?.Prepare(tokens)); user = accountCreateOutput.User; //continue with the org setup //see what apps the client api wants to register var appIdentifiers = input.LicenseOptions.Keys.ToArray(); //get them... var apps = await dbCtx.Applications.Where(a => appIdentifiers.Contains(a.ShortName)).ToListAsync(); //always make sure to glue in the core apis and apps apps.AddRange( MapHive.Core.Defaults.Applications.GetDefaultOrgApps() ); foreach (var appShortName in input.LicenseOptions.Keys) { var app = apps.FirstOrDefault(a => a.ShortName == appShortName); if (app == null) { continue; } newOrg.LicenseOptions.Add( new OrganizationLicenseOption { LicensedObjectTypeUuid = app.TypeUuid, LicensedObjectUuid = app.Uuid, LicenseOptions = input.LicenseOptions[appShortName] } ); } //create an org with owner and register the specified apps //make sure though to use the collection that contains the the default org apps! await newOrg.CreateAsync(dbCtx, user, apps); //wire up user with his 'parent org' //this is so it is clear a user has his own org user.UserOrgId = newOrg.Uuid; await user.UpdateAsync(dbCtx); return(new AccountCreateOutput { EmailTemplate = input.EmailTemplate, VerificationKey = accountCreateOutput.VerificationKey, InitialPassword = accountCreateOutput.InitialPassword }); }
protected virtual async Task Handle_AddSuper(Dictionary <string, string> args) { var cmd = GetCallerName(); args = args ?? new Dictionary <string, string>(); if (GetHelp(args)) { Console.WriteLine($"'{cmd}' : adds a maphive superuser to the system. expects the master of puppets app to be registered; will fail if it is not."); Console.WriteLine($"syntax: {cmd} space separated params: "); Console.WriteLine("\t[e:email]"); Console.WriteLine("\t[p:pass]"); Console.WriteLine("\t[s:slug] user's slug"); Console.WriteLine("\t[o:{presence}] whether or not user is an org user"); Console.WriteLine(); Console.WriteLine($"example: {cmd} e:[email protected] p:test"); return; } var email = ExtractParam("e", args); var pass = ExtractParam("p", args); var slug = ExtractParam("s", args); var isOrgUser = ContainsParam("o", args); //use the default account if email and pass not provided if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(pass)) { email = "*****@*****.**"; pass = "******"; } if (!isOrgUser && string.IsNullOrEmpty(slug)) { slug = email.Split('@')[0]; } //delegate user creation var prms = new Dictionary <string, string>() { { "e", email }, { "p", pass }, { "s", slug } }; if (isOrgUser) { prms.Add("o", null); } await Handle_AddUser(prms); try { ConsoleEx.Write("Setting up super user access... ", ConsoleColor.DarkYellow); var ctx = new MapHiveDbContext("MapHiveMeta"); //create the master org if not exists! var org = await ctx.Organisations.FirstOrDefaultAsync(o => o.Slug == "thehive"); if (org == null) { org = await new Organisation { Slug = "thehive", DisplayName = "The Hive" }.CreateAsync(ctx); //get the maphive admin app var masterofpuppets = await ctx.Applications.FirstOrDefaultAsync(a => a.ShortName == "masterofpuppets"); //add a link to a non-public app! //users with access to this organisation will be able to access this app too //note: perhaps with some app access privs defined at some point org.AddLink(masterofpuppets); await org.UpdateAsync(ctx); } //get user by email var user = await ctx.Users.FirstOrDefaultAsync(u => u.Email == email); //assign user to the org, so user is visible 'within' an org org.AddLink(user); await org.UpdateAsync(ctx); //assing the master org owner role to a user var orgOwnerR = await org.GetOrgOwnerRoleAsync(ctx); user.AddLink(orgOwnerR); //finally save a user await user.UpdateAsync(ctx, CustomUserAccountService.GetInstance("MapHiveMbr")); ConsoleEx.Write("Done!" + Environment.NewLine, ConsoleColor.DarkGreen); Console.WriteLine(); } catch (Exception ex) { HandleException(ex); return; } }
/// <summary> /// Reads licence options for a range of orgs /// </summary> /// <param name="orgs"></param> /// <param name="dbCtx"></param> /// <returns></returns> public static async Task ReadLicenseOptionsAsync(this IEnumerable <Organization> orgs, MapHiveDbContext dbCtx) { //TODO - make it possible to read org assets in bulk in two scenarios //TODO - * for a single org //TODO - * for a range of orgs //TODO - this is so the license opts read is quick and optimised instead of reading all the stuff one by one. it should be enough to read all the packages, modules, apps and datasources at in single reads and then just pass the data for further processing //for the above also see the Assets.cs - this is where the optimisation will take place //for the time being just extracting all the stuff one by one... foreach (var org in orgs) { await org.ReadLicenseOptionsAsync(dbCtx); } }
/// <summary> /// Loads dbs configured for an org /// </summary> /// <param name="organization"></param> /// <param name="db"></param> public static void LoadDatabases(this Organization organization, MapHiveDbContext db) { organization.Databases = db.OrganizationDatabases.Where(x => x.OrganizationId == organization.Uuid).ToList(); }
/// <summary> /// Loads dbs configured for an org /// </summary> /// <param name="organization"></param> /// <param name="db"></param> /// <returns></returns> public static async Task LoadDatabasesAsync(this Organization organization, MapHiveDbContext db) { organization.Databases = await db.OrganizationDatabases.Where(x => x.OrganizationId == organization.Uuid).ToListAsync(); }
/// <summary> /// Gets applications assigned to token /// </summary> /// <param name="dbCtx"></param> /// <returns></returns> public async Task <List <Application> > GetApplicationsAsync(MapHiveDbContext dbCtx) { ApplicationIds ??= new SerializableListOfGuid(); return(await dbCtx.Applications.Where(app => ApplicationIds.Contains(app.Uuid)).ToListAsync()); }
/// <summary> /// Gets organization assigned to token /// </summary> /// <param name="dbCtx"></param> /// <returns></returns> public async Task <Organization> GetOrganizationAsync(MapHiveDbContext dbCtx) { return(await dbCtx.Organizations.FirstOrDefaultAsync(org => org.Uuid == OrganizationId)); }
/// <summary> /// grabs a default language /// </summary> /// <param name="dbCtx"></param> /// <returns></returns> protected async Task <string> GetDefaultLangCodeAsync(MapHiveDbContext dbCtx) { return((await dbCtx.Langs.FirstOrDefaultAsync(l => l.IsDefault))?.LangCode); }
public static void SeedEmailLocalisations(MapHiveDbContext context) { var activateAccountLink = $"{{RedirectUrl}}#{WebClientConfiguration.AppHashProperties["auth"]}{WebClientConfiguration.HashPropertyValueDelimiter}activateaccount{WebClientConfiguration.HashPropertyDelimiter}{WebClientConfiguration.AppHashProperties["verificationKey"]}{WebClientConfiguration.HashPropertyValueDelimiter}{{VerificationKey}}{WebClientConfiguration.HashPropertyDelimiter}{WebClientConfiguration.AppHashProperties["initialPassword"]}{WebClientConfiguration.HashPropertyValueDelimiter}{{InitialPassword}}"; var resetPassLink = $"{{RedirectUrl}}#{WebClientConfiguration.AppHashProperties["auth"]}{WebClientConfiguration.HashPropertyValueDelimiter}resetpass{WebClientConfiguration.HashPropertyDelimiter}{WebClientConfiguration.AppHashProperties["verificationKey"]}{WebClientConfiguration.HashPropertyValueDelimiter}{{VerificationKey}}"; context.EmailTemplates.AddOrUpdate( new EmailTemplateLocalisation { Uuid = Guid.Parse("ee624417-8565-49c8-b19a-83f3b0da8550"), Name = "User created", Description = @"Email sent when user has been created. Replacement tokens are: {InitialPassword}, {VerificationKey}, {UserName}, {Email} {RedirectUrl} ", Identifier = "user_created", IsBodyHtml = true, Translations = new EmailTranslations { { "en", new EmailTemplate { Title = "[[email protected]] MapHive user account created for {UserName}.", Body = $@"<h3>Hi {{UserName}},</h3> <p>MapHive user account has been created for you.</p> <p>Your initial password is: <b>{{InitialPassword}}</b></p> <p>and your verification key is: <b>{{VerificationKey}}</b></p> <br/> <p>In order to activate your account please click <a href={activateAccountLink}>here</a> or paste the following link in your browser: {activateAccountLink}</p> <br/> <p>Kind regards<br/>MapHive Team</p>" } } } }, //account activation verification key stale new EmailTemplateLocalisation { Uuid = Guid.Parse("e74b24a5-b541-4cb5-87d3-fb98bc83c541"), Name = "Account activation verification key stale", Description = @"Email sent when account activation is not possible due to a verication key being stale. Replacement tokens are: {InitialPassword}, {VerificationKey}, {UserName}, {Email} {RedirectUrl} ", Identifier = "activate_account_stale", IsBodyHtml = true, Translations = new EmailTranslations { { "en", new EmailTemplate { Title = "[[email protected]] MapHive account activation required for {UserName}.", Body = $@"<h3>Hi {{UserName}},</h3> <p>MapHive user account could not have been activated due to the verification key becoming outdated.</p> <p>Your new verification key : <b>{{VerificationKey}}</b></p> <p>Please use the initial password sent previously.</p> <br/> <p>In order to activate your account please click <a href={activateAccountLink}>here</a> or paste the following link in your browser: {activateAccountLink}</p> <br/> <p>Kind regards<br/>MapHive Team</p>" } } } }, //password reset email new EmailTemplateLocalisation { Uuid = Guid.Parse("0e172a1a-a4f3-4058-9456-ef5af61b778f"), Name = "Password reset request", Description = @"Email sent when user cannot login and decides to reset his pass. Replacement tokens are: {VerificationKey}, {UserName}, {Email} {RedirectUrl} ", Identifier = "pass_reset_request", IsBodyHtml = true, Translations = new EmailTranslations { { "en", new EmailTemplate { Title = "[[email protected]] MapHive account password reset requested for {UserName}.", Body = $@"<h3>Hi {{UserName}},</h3> <p>MapHive account password reset has been requested for your account. If you have not requested a password reset then please ignore this email as nothing has changed for you.<br/>Otherwise please follow the instructions below to complete a password reset request.</p> <br/> <p>In order to reset your current password please click <a href={resetPassLink}>here</a> or paste the following link in your browser: {resetPassLink}</p> <br/> <p>Kind regards<br/>MapHive Team</p>" } } } } ); }