I know this question is asked already many times, but I did not find a good answer for my case. I'm using SQLC to generate methods for querying the db. Everything is working fine when using one connection initialised at start. Now I need to set it up in a multi-tenant environment where each tenant will have a separate DB. For now I would like to start with a connection map (map[string]*sql.DB) connecting the tenant with a database connection. My question is about overriding/selecting the connection at runtime. with one connection the repository is initialised like:
type Repository interface {
GetCustomerById(ctx context.Context, id int64) (Customer, error)
ListCustomers(ctx context.Context) ([]Customer, error)
}
type repoSvc struct {
*Queries
db *sql.DB
}
func NewRepository(dbconn *sql.DB) Repository {
return &repoSvc{
Queries: New(dbconn),
db: dbconn,
}
}
customerRepo := customerRepo.NewRepository(conn)
GetCustomerById is the SQLC generated method
conn is the database connection
How to make the connection based on a parameter (from cookie or context)?
The simplest way, assuming you are using separate databases, is to maintain a map[tenantID]Repository, where tenantID is the way you differentiate between tenants (e.g. a string or uint that contains the tenant ID).
This way you can do everything at runtime:
when you need to add a tenant, just instantiate the Repository for that tenant and add it to the map
when you need to remove a tenant, just remove its Repository from the map and close the DB connection
when you need to perform a query for a tenant, lookup the corresponding Repository in the map, and use it to perform the query for that tenant
If the operations above may happen concurrently, make sure that you're using some synchronization mechanism to avoid data races when accessing the map (e.g. sync.Map, or sync.RWMutex).
If you have a database table that stores the tenants and their DB connection URIs, you can still use this approach: when you need to perform a query check if the Repository exists in the map: if it's missing, query the tenant table and add the Repository for that tenant to the map. You can then periodically scan the map and remove any Repository that has not been used for some time.
To make all of this easier you could also wrap the whole machinery into a MultitenantRepository interface, that is identical to the Repository interface but that accepts an additional tenantID parameter on each method:
type MultitenantRepository interface {
GetCustomerById(ctx context.Context, tenant tenantID, id int64) (Customer, error)
ListCustomers(ctx context.Context, tenant tenantID) ([]Customer, error)
}
This will avoid exposing all the complexity of your multitenant setup to your business logic.
Related
I need to declare 2 IDPs in spring-security-saml having the same entity id.
My webapp uses spring-security-saml.
This webapp is accessible by 2 differents URLs behind a reverse proxy.
The first URL is public, the second URL is filtered.
So, I declared 2 SP (one for each URL).
Everything was working properly with a single IDP (ADFS or Gsuite).
I also run the application properly with 2 SPs and 2 IDPs with an affinity SP1/IDP1 and SP2/IDP2 when IDP1 and IDP2 had a different entity ID.
Unfortunately by wanting to use Azure Active Directory, each SAML application in Azure results in its own IDP metadata with its own certificate, but with the same entity id.
So I need to declare 2 IDPs in spring-security-saml having the same entity id.
Reading the code shows that it is not intended to work like this (the entity id is used as key).
Do you have an idea to work around this problem?
Should Azure provide a unique entity id ?
I know it is too old but just found it but you can not use the same Entity ID per tenant for 2 different apps, so it makes sense that the apps have a different certificate even if they have same Entity ID because both apps are in different tenants
How it worked for me!!
As Spring saml works only for unique IDP entityIds. So to make it unique for 2 different IDP having same entity Ids, I prexied one of it with alias as i know what is that alias is for.
So now I have to hack entityID at certain places of initialization, validation during metadata loading AND in SAML response verification.
For metadata(one that has prefixed entity Id) loading to be successful especially one with signed metadata..
Created new child class MySAMLSignatureProfileValidator that overrides
SAMLSignatureProfileValidator.validateReferenceURI.
To use this I need to create another custom class SamlSignatureValidationFilter that extends MYSamlSignatureValidationFilter and initialise MySAMLSignatureProfileValidator in their constructor.
Use this SamlSignatureValidationFilter when we add metadata to metadata manager like this..
metadataProvider.setMetadataFilter(new MYSamlSignatureValidationFilter(metadata.getTrustEngine(metadataProvider)));
And now add another custom class MYSAMLCachingMetadataManager to override initializeProviderFilters and remove the logic to setMetadataFilter as its already set as in above code.
Use MYSAMLCachingMetadataManager in your config for MetadataManager.
This should take care of saml metadata loading.
Then coming to SAML Response that has the issuer as the original entityId, we need to add prefixed alias to the context here so that it verifies with our prefixed_entityId stored in metadatamanager entity list.
In this case I added MySamlHttpPostDecoder that overrides HttpPostDecoder.extractResponseInfo to add alias to messageIssuer.
And, MySamlWebSSOProfileConsumerImpl to overirde WebSSOProfileConsumerImpl.verifyIssuer to set issuer.getValue with alias. so later verification with stored entitId will match.
Use this MySamlWebSSOProfileConsumerImpl and MySamlHttpPostDecoder in your config. To use MySamlHttpPostDecoder I need to add new class MySamlHTTPPostBinding(ParserPool parserPool, VelocityEngine velocityEngine, MessageDecoder decoder) that extends HTTPPostBinding and pass MySamlHttpPostDecoder for decoder.
Hope it works for you too!!!
I'm currently developing a back end and having my first run in with security laws etc. and it has complicated the design of my DB slightly:
Specification
Central server for app with DB containing limited user information (user_id, email, password (hashed and salted)) can be anywhere.
Organisations making use of our service require that all other information be stored in-house, so the database for that particular organisation is in their building.
The user IDs in our central database are used by multiple types of users in these organisations databases, where more info about that user is stored (phone number, name, address...)
Problem
With Spring Boot, I need to make it so the datasource used is determined by which user makes the request. I map users to their corresponding organisation's database within the central server so the information is there, but I'm not sure how to make this variable.
I understand there are methods involving adding another database config in the application.properties file. But as far as I'm aware this can't be changed (easily) once the server is deployed and running without a full redeploy, and I'm hoping to build this in such a way that adding another organisation only involves setting up their db, and adding another database details to the central server.
Extra detail
I'd like to use CrudRepository with hibernate entities for this. I plan on only generating user IDs on the central server.
Any pointers would be awesome.
Thanks!
The terminology for this is database multi-tenancy. There are multiple strategies for multi-tenancy: different databases, different schemas in the same database, and the same schema on one database with a defined discriminator.
You basically create a DataSourceBasedMultiTenantConnectionProviderImpl class which provides the connection to a datasource based on which tenant is requesting it, and a CurrentTenantIdentifierResolverImpl class which identifies who is the requesting tenant.
You can read more about it here. Since your tenants each have their own database, you would probably want to focus on the multi-tenancy separate database approach. It worked fine with CrudRepository when I implemented it. You also might want to find your own way of creating the tenant map, since I had 2 tenants and no need to add more at any point.
Heres a sample of the connection provider from when I implemented this:
public class DataSourceBasedMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final String DEFAULT_TENANT_ID = "A";
#Autowired
private DataSource datasourceA;
#Autowired
private DataSource datasourceB;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("A", datasourceA);
map.put("B", datasourceB);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}
The sample and seed data shows creating a new client in Startup.
This is fine in case of creating a client.
Are there any existing methods or provision for Updating a client. Update involves tracking the existing records from the collection fields within the clients too.
How are entities mapped from IdentityServer4.Models to IdentityServer4.EntityFramework.Entities during an update considering the records are already available in database?
What do you mean when you say client?? If you mean Client for Identity server then you can edit/configure or add more clients or other resources in your config class. While startup, identity server will loads up all the clients by itself, all because of this code:
// Add identity server.
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetInMemoryIdentityResources())
.AddInMemoryApiResources(Config.GetInMemoryApiResources())
.AddInMemoryClients(Config.GetInMemoryClients(Configuration))
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<SqlProfileService>();
Are there any existing methods or provision for Updating a client.
Update involves tracking the existing records from the collection
fields within the clients too
Yes, you can update client as you can update any other data. Check here how you can use EntityFramework core with identityserver4
How are entities mapped from IdentityServer4.Models to
IdentityServer4.EntityFramework.Entities during an update considering
the records are already available in database?
If you check the IdentityServer4 source you will find AutoMapper is used to convert entities (namespace IdentityServer4.EntityFramework.Mappers). And an extension named ToModel has been provided
Some weeks ago I discovered OpenID Connect and IdentityServer V3. I ran some of the supplied examples and I am today at the point where I need to go further but I don't know how to proceed:
Actually we have an "home made" authentication and authorization process and we'd like to move to an OpenID Connect solution. Identity Server seems to be the perfect candidate to do this.
Today our users are stored into an SQL Server Database and ideally I'd like to "connect" this table to Identity Server (without touching to the schema of this table). I read about "MembershipReboot" but it uses its own Database. I also heard about making a custom user service but in the sample (CustomUserService) I did not find anything helpfull. Today I'am a little bit lost because I don't know where to go and I realize that I'am not very far from the target.
I need help
Thank you
In the Custom User Service Sample you mentioned, it includes three variations of user service to show different approaches, but you really only need one of them.
In the LocalRegistrationUserService sample you'll find lines like these:
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = Users.SingleOrDefault(x => x.Username == context.UserName && x.Password == context.Password);
/// snip ...
and these:
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// issue the claims for the user
var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
/// snip...
You need to replace those calls which look up values from the in-memory Users collection with something that opens a connection to SQL server and looks them up there instead.
See the Custom User Service documentation for more methods supported, but those two (AuthenticateLocalAsync, GetProfileDataAsync) plus your SQL lookup are all you need to get started.
I'm testing out the recently released DocumentDb and can't find any documentation indicating best practice on how to perform user data segregation.
I imagine the rough design would be:
Authenticate the user and create new/obtain existing user id
On document insert inject the user id into the document
On read of document/collection of documents query where document user id = current user id
I'm creating an AngularJs application and currently use an Azure Sql Database combined with Azure Mobile Services.
Mobile services handles the user authentication and also the server side user data segregation by the use of data script javascript functions:
e.g.
function insert(item, user, request) {
item.userId = user.userId;
request.execute();
}
Any suggestions on what would be the technique for secure user data segregation from AngularJS using DocumentDB?
Your approach sounds reasonable to me - assuming the logic mentioned in your rough design takes place in your backend service.
Generally, I'd treat DocumentDB similarly as you would treat any other datastore. Your client (AngularJS) makes calls to your backend service, rather than making calls directly to your datastore. Your backend validates the client's request (i.e. assert that the user is authenticated and may touch a particular piece of data) before delegating any work to your datastore.
If direct database access from the client is desired - you can check out DocumentDB's users and permissions. For implementing multi-tenancy for your application, you can create users in DocumentDB which corresponds to your actual users or the tenants of your application. You can then create permissions for a given user which correspond to the access control over various collections, documents, attachments etc. On your client, you can connect to the database using the User's resource key rather than your DocumetnDB's administrator keys.
Check out this blog post on DocumentDB users / permissions: http://blogs.msdn.com/b/cloud_solution_architect/archive/2014/12/09/permissions-in-azure-documentdb.aspx