Firebase multi-tenancy with play framework depends on header value of HTTP request

48 views
Skip to first unread message

Al-Mothafar Al-Hasan

unread,
Jun 20, 2017, 10:22:11 PM6/20/17
to Play Framework
Hi, I hope you good, may I ask for a little help please with my issue here, I have a play framework project that provides APIs that shared between multiple front-ends, currently, I'm working on single front-end but I want to create a multi-tenant backend, each front-end got its own Firebase account.

My problem that I have to consider which firebase project to access depends on the request header value, that came with different values depends on the front end.

What I have now:
FirebaseAppProvider.java

public class FirebaseAppProvider implements Provider<FirebaseApp> {


    private final Logger.ALogger logger;
    private final Environment environment;
    private final Configuration configuration;

    @Inject
    public FirebaseAppProvider(Environment environment, Configuration configuration) {
        this.logger = Logger.of(this.getClass());
        this.environment = environment;
        this.configuration = configuration;
    }

    @Singleton
    @Override
    public FirebaseApp get() {
        HashMap<String, String> firebaseProjects = (HashMap<String, String>) configuration.getObject("firebase");
        firebaseProjects.forEach((websiteId, projectId) -> {
            FileInputStream serviceAccount = null;
            try {
                serviceAccount = new FileInputStream(environment.classLoader().getResource(String.format("firebase/%s.json", projectId)).getPath());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return;
            }

            FirebaseOptions options = new FirebaseOptions.Builder().setCredential(FirebaseCredentials.fromCertificate(serviceAccount))
                    .setDatabaseUrl(String.format("https://%s.firebaseio.com/", projectId))
                    .build();


            FirebaseApp firebaseApp = FirebaseApp.initializeApp(options, projectId);

            logger.info("FirebaseApp initialized");
        });

        return FirebaseApp.getInstance();
    }
}

Also for Database:
FirebaseDatabaseProvider.java
    
public class FirebaseDatabaseProvider implements Provider<FirebaseDatabase> {

    private final FirebaseApp firebaseApp;
    public static List<TaxItem> TAXES = new ArrayList<>();

    @Inject
    public FirebaseDatabaseProvider(FirebaseApp firebaseApp) {
        this.firebaseApp = firebaseApp;
        fetchTaxes();
    }

    @Singleton
    @Override
    public FirebaseDatabase get() {
        return FirebaseDatabase.getInstance(firebaseApp);
    }

    @Singleton
    public DatabaseReference getUserDataReference() {
        return this.get().getReference("/usersData");
    }

    @Singleton
    public DatabaseReference getTaxesConfigurationReference() {
        return this.get().getReference("/appData/taxConfiguration");
    }
    private void fetchTaxes() {
        DatabaseReference bundlesRef = getTaxesConfigurationReference().child("taxes");
        bundlesRef.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                TAXES.clear();
                dataSnapshot.getChildren().forEach(tax -> TAXES.add(tax.getValue(TaxItem.class)));
                Logger.info(String.format("==> %d taxes records loaded", TAXES.size()));
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Logger.warn("The read failed: " + databaseError.getCode());
            }
        });
    }
}

So I bind them as well from Module.java:

public class Module extends AbstractModule {

    @Override
    public void configure() {        bind(FirebaseApp.class).toProvider(FirebaseAppProvider.class).asEagerSingleton();
        bind(FirebaseAuth.class).toProvider(FirebaseAuthProvider.class).asEagerSingleton();
        bind(FirebaseDatabase.class).toProvider(FirebaseDatabaseProvider.class).asEagerSingleton();
    }

}

my ActionCreator:

public class ActionCreator implements play.http.ActionCreator {

    @Inject
    public ActionCreator() {
    }

    @Override
    public Action createAction(Http.Request request, Method actionMethod) {
        switchTenancyId(request);
        return new Action.Simple() {
            @Override
            public CompletionStage<Result> call(Http.Context ctx) {
                return delegate.call(ctx);
            }
        };
    }

    private void switchTenancyId(Http.RequestHeader request) {
        // DO something here
    }

    private Optional<String> getTenancyId(Http.RequestHeader request) {
        String websiteId = request.getHeader("Website-ID");
        System.out.println(websiteId);
        return null;
    }
}

What I want is when I use Database service, or auth service, I read the website id and decide which firebase project to access, I really tried the solution like this answer here:

Please note I'm willing to use differents projects, not the same firebase project for each front-end.

But kinda lost, especially the request can be only accessed from controller or ActionCreator, so what I got from the question above is load providers by key into `ThreadLocal` and switch them for each request depends on the annotation, but I was unable to do this because of the lack of knowledge.


----------


The minimized version of my project can be found here: https://github.com/almothafar/play-with-multi-tenant-firebase 

Also, I uploaded `taxes-data-export.json` file to import inside firebase project for a test.
Reply all
Reply to author
Forward
0 new messages