Building Mattermost integration with Laravel. Part 2— Building services.
In this series of articles we’re building integration between popular corporate chat application Mattermost and project management system Redmine.
By the end of the first part we have configured project environment to connect to both Mattermost and Redmine and highlighted some implementation details of our future integration.
Let’s review some of the most important of this details:
- Both Mattermost and Redmine are connected via API tokens
- Integration is accessed from Mattermost via slash commands
- Recommended library is used to interact with Redmine
- Besides built-in functionality we’re creating custom time trackers
To achieve our goals we’ll be using one of the most powerful and (in my opinion) most beautiful feature of Laravel: combination of Services, Service Providers and Middleware.
Laravel Service will be used to implement APIs. Remember one of the OOP concepts: code to an interface, not to implementation. In this case, think of a service as implementation of external interface (API).
Service Providers will help us to inject created services to controllers automatically. We won’t make any direct calls to APIs, instead we’ll use our services as local objects.
Last but not least, Middleware will be responsible for verifying requests by tokens and for one of the most challenging tasks in integration with team applications: user impersonation.
After services and middlewares are built, we’ll have an ability to create a set of controllers in a most simple manner, treating external resources as internal and processing requests natively.
Laravel, of course, has more or less comprehensible documentation over its service providers, so we won’t dive into theory. Sufficient to say that service provider can instantiate an object of given class globally, thus making it possible for service container to inject it into any step in application lifecycle (we’ll be injecting them in Controllers).
First, let’s create a service provider for Mattermost via Artisan command.
php artisan make:provider MattermostServiceProvider
Don’t forget that any service provider must be registered in
config/app.php as stated in Laravel docs.
Next, we’ll create a service itself. For now service will contain only constructor and methods to establish and validate connection to Mattermost.
Notice array of credentials in constructor arguments. Thanks to created configurations from previous article we are now able to provide these credentials to instantiate new service. But not manually. That’s where service provider comes in. Using service provider we can not only inject services, but also avoid duplicate code of instantiating it. In this scenario, we need to implement single
register() method of
MattermostServiceProvider. Consider the following lines.
Almost done with Mattermost service with only middleware to be implemented. In order to verify requests we need to retrieve a token and compare it to stored one. First, we create a middleware.
php artisan make:middleware VerifyMattermostToken
And then implement
By this time we have fully implemented a service to connect and interact with Mattermost API using stored credentials and token management. One can safely implement any of its API methods in
Example of API method implementation.
Building service for Redmine API is basically the same as building Mattermost service but with couple of tricky things. I’d like to highlight those instead of simply providing mostly identical code.
- First of all our integration logic implies that requests from Mattermost (which acts as integration frontend) will be processed and used to modify data in Redmine. Since Redmine is project management system and has a complex role-based access control, we definitely don’t want to allow any chat user to have admin access. So the first challenge is to fetch username from Mattermost request before any interaction with Redmine.
- After fetching username we need to ensure that any request from our integration to Redmine API contain user credentials, so Redmine would take care of accesses for us. Luckily, Redmine actually does provide built-in impersonation feature.
Fetching username from Mattermost request
To address the first issue we need to head back to Mattermost for a while and understand the structure of slash command request.
Original request sent by slash command is very simple. All required parameters are passed with query string, including
user_name, which we’re looking for. Unfortunately we can’t inject query parameters directly in Redmine service. Details of integrating them together will be the subject of next article but we can already say that we will need some kind of request proxy and redirecting.
After the request is redirected, we can still access original data, not from query string but from
Referer header. It requires some additional actions and error handling, so I’d suggest we write a little static Helper for parsing headers.
Now we can retrieve original
user_name parameter even if initial request was redirected.
Implementing Redmine impersonation
Impersonation must be done by default. Any other scenario can result in security breach, so the obvious solution would be hardcoding it in service provider. This way any instance of Redmine service injected by service container is impersonated. With this solution service provider will be a little bit more complicated than service provider for Mattermost.
Notice that we didn’t implement any service method. Instead, we’re using recommended library whereas service is used only as wrapper class.
All is left to do at this moment is to re-implement almost same structure of service class for Redmine as we did for Mattermost and middleware to verify API tokens. After that everything is ready for finishing our integration.
In next (and last) article of this series we’ll create a set of controllers to manage resources and redirecting requests between two external apps.
Thank you for taking the time to read this. Subscribe!