decidim-msad 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +476 -0
  4. data/Rakefile +17 -0
  5. data/app/controllers/decidim/msad/omniauth_callbacks_controller.rb +178 -0
  6. data/app/controllers/decidim/msad/sessions_controller.rb +58 -0
  7. data/app/controllers/decidim/msad/verification/authorizations_controller.rb +19 -0
  8. data/config/locales/en.yml +27 -0
  9. data/config/locales/fi.yml +26 -0
  10. data/config/locales/sv.yml +26 -0
  11. data/lib/decidim/msad.rb +198 -0
  12. data/lib/decidim/msad/authentication.rb +4 -0
  13. data/lib/decidim/msad/authentication/authenticator.rb +229 -0
  14. data/lib/decidim/msad/authentication/errors.rb +21 -0
  15. data/lib/decidim/msad/engine.rb +112 -0
  16. data/lib/decidim/msad/mail_interceptors.rb +9 -0
  17. data/lib/decidim/msad/mail_interceptors/generated_recipients_interceptor.rb +25 -0
  18. data/lib/decidim/msad/test/runtime.rb +38 -0
  19. data/lib/decidim/msad/verification.rb +5 -0
  20. data/lib/decidim/msad/verification/engine.rb +43 -0
  21. data/lib/decidim/msad/verification/manager.rb +17 -0
  22. data/lib/decidim/msad/verification/metadata_collector.rb +39 -0
  23. data/lib/decidim/msad/version.rb +8 -0
  24. data/lib/generators/decidim/msad/install_generator.rb +126 -0
  25. data/lib/generators/templates/msad_initializer.rb +74 -0
  26. data/lib/generators/templates/msad_initializer_test.rb +5 -0
  27. data/lib/omniauth/msad/metadata.rb +46 -0
  28. data/lib/omniauth/msad/settings.rb +9 -0
  29. data/lib/omniauth/msad/test.rb +10 -0
  30. data/lib/omniauth/msad/test/certificate_generator.rb +51 -0
  31. data/lib/omniauth/strategies/msad.rb +187 -0
  32. metadata +116 -0
@@ -0,0 +1,476 @@
1
+ # Decidim::Msad - Integrate Decidim to Microsoft Active Directory (AD)
2
+
3
+ [![Build Status](https://travis-ci.com/mainio/decidim-module-msad.svg?branch=master)](https://travis-ci.com/mainio/decidim-module-msad)
4
+ [![codecov](https://codecov.io/gh/mainio/decidim-module-msad/branch/master/graph/badge.svg)](https://codecov.io/gh/mainio/decidim-module-msad)
5
+ [![Crowdin](https://badges.crowdin.net/decidim-msad/localized.svg)](https://crowdin.com/project/decidim-msad)
6
+
7
+ A [Decidim](https://github.com/decidim/decidim) module to add Microsoft Active
8
+ Directory (AD) authentication to Decidim as a way to authenticate and authorize
9
+ the users. Can be integrated to Azure AD or ADFS running on the organization's
10
+ own server using the SAML authentication flow (SAML 2.0).
11
+
12
+ This allows Decidim users to log in to Decidim using their organization's Active
13
+ Directory accounts, which are usually the same accounts they use to log in to
14
+ their computers. In addition, these users can also be authorized with the data
15
+ available in the AD server (person's department, location, groups, etc.).
16
+
17
+ The gem has been developed by [Mainio Tech](https://www.mainiotech.fi/).
18
+
19
+ Active Directory is a Microsoft product and is not related to this gem in any
20
+ way, nor do they provide technical support for it. Please contact the gem
21
+ maintainers in case you find any issues with it.
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem "decidim-msad"
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ $ bundle
35
+ ```
36
+
37
+ After installation, you can add the initializer running the following command:
38
+
39
+ ```bash
40
+ $ bundle exec rails generate decidim:msad:install
41
+ ```
42
+
43
+ You need to set the following configuration options inside the initializer:
44
+
45
+ - `:idp_metadata_url` - The metadata URL for the identity provider which is the
46
+ federation server's metadata URL.
47
+ - `:sp_entity_id` - The service provider entity ID, i.e. your applications
48
+ entity ID used to identify the service at the Active Directory SAML identity
49
+ provider.
50
+ * Set this to the same ID that you use for the metadata sent to the federation
51
+ server.
52
+ * Default: depends on the application's URL, e.g.
53
+ `https://www.example.org/users/auth/msad/metadata`
54
+
55
+ Optionally you can also configure the module with the following options:
56
+
57
+ - `:auto_email_domain` - Defines the auto-email domain in case the user's domain
58
+ is not stored at the federation server. In case this is not set (default),
59
+ emails will not be auto-generated and users will need to enter them manually
60
+ in case the federation server does not report them.
61
+ * The auto-generated email format is similar to the following string:
62
+ `msad-756be91097ac490961fd04f121cb9550@example.org`. The email will
63
+ always have the `msad-` prefix and the domain part is defined by the
64
+ configuration option.
65
+ * In case this is not defined, the organization's host will be used as the
66
+ default.
67
+ - `:certificate_file` - Path to the local certificate included in the metadata
68
+ sent to the federation server with the service provider metadata. This is
69
+ optional as Azure AD or ADFS do not force encrypting the SAML assertion data.
70
+ - `:private_key_file` - Path to the local private key (corresponding to the
71
+ certificate). Will be used to decrypt messages coming from the federation
72
+ server. As the `:certificate_file` option, this is also optional.
73
+ - `:metadata_attributes` - Defines the SAML attributes that will be stored in
74
+ the user's associated authorization record's `metadata` field in Decidim.
75
+ These are read from the SAML authentication response and stored once the user
76
+ is identified or a new user record is created. See an example in the
77
+ initializer file.
78
+ - `:sp_metadata` - Extra metadata that you can add to the service provider
79
+ metadata XML file. See an example in the initializer file.
80
+
81
+ The install generator will also enable the Active Directory authentication
82
+ method for OmniAuth by default by adding these lines your `config/secrets.yml`:
83
+
84
+ ```yml
85
+ default: &default
86
+ # ...
87
+ omniauth:
88
+ # ...
89
+ msad:
90
+ enabled: false
91
+ metadata_url:
92
+ icon: account-login
93
+ development:
94
+ # ...
95
+ omniauth:
96
+ # ...
97
+ msad:
98
+ enabled: true
99
+ metadata_url:
100
+ icon: account-login
101
+ ```
102
+
103
+ This will enable the Active Directory authentication for the development
104
+ environment only. In case you want to enable it for other environments as well,
105
+ apply the OmniAuth configuration keys accordingly to other environments as well.
106
+
107
+ Please also note that you will need to define the metadata URL for the identity
108
+ provider (the federation server, either Azure AD or ADFS) in order for the
109
+ integration to work.
110
+
111
+ The example configuration will set the `account-login` icon for the the
112
+ authentication button from the Decidim's own iconset. In case you want to have a
113
+ better and more formal styling for the sign in button, you will need to
114
+ customize the sign in / sign up views.
115
+
116
+ ## Configuring the federation server
117
+
118
+ For configuring the federation server, you will need to send the AD federation
119
+ server administrator some data from the Decidim's side. After installing the
120
+ module and configuring your entity ID, possible SP certificate and any additonal
121
+ SP metadata you want to send, you can send the federation side's administrator
122
+ the following URL to your instance in order for them to get the necessary data
123
+ for joining the system at their side:
124
+
125
+ https://your-organization.org/users/auth/msad/metadata
126
+
127
+ Change the domain accordingly to your instance and make sure you see an XML file
128
+ in that URL when you request it. This is the SAML metadata you will need to send
129
+ to the other side.
130
+
131
+ After the other side is configured, you can start using the integration at
132
+ Decidim's side. If you already know the AD federation server's metadata URL in
133
+ advance, you can already configure it at this point but obviously the sign ins
134
+ won't work until the federation server is configured correctly. The metadata
135
+ URLs for the federation servers (either Azure AD or ADFS) should look as
136
+ follows:
137
+
138
+ - **Azure AD**: https://login.microsoftonline.com/123a4567-8b90-12a3-45b6-789012a345bc/federationmetadata/2007-06/federationmetadata.xml?appid=ab1c2d3e-45fa-123b-4cd5-e678fabc90d1
139
+ - **ADFS**: https://adfs.your-organization.org/FederationMetadata/2007-06/FederationMetadata.xml
140
+
141
+ You can configure this URL to the module's `:idp_metadata_url` configuration as
142
+ explained previously in this document. Before giving it a try, you of course
143
+ need to configure the federation side to handle your requests.
144
+
145
+ If you don't know the metadata URL, please ask it from the federation server
146
+ administrator after you have sent your service provider (SP) metadata to them.
147
+
148
+ ### Configuring Azure AD
149
+
150
+ To configure Azure AD, follow these steps:
151
+
152
+ 1. Go to your Decidim instance's metadata URL (explained previously in this
153
+ document) and save the metadata XML to a file with the `.xml` extension on
154
+ your computer.
155
+ 2. In Azure, may be obvious but deploy the Azure AD instance if you haven't done
156
+ so yet.
157
+ 3. Under the Azure Active Directory, go to "Enterprise Applications".
158
+ 4. Click "+ New application" and from the top of the view that opens, click
159
+ "+ Create your own application".
160
+ 5. Give the name of your app based on your application's name, e.g.
161
+ "Your Organization Decidim" and leave the "Integrate any other application
162
+ you don't find in the gallery" option selected.
163
+ 6. Go to the "Single sign-on" tab and select "SAML" as the Select a single
164
+ sign-on method.
165
+ 7. From the top of the view, click "Upload metadata" and pick the Decidim
166
+ instance's metadata XML file you saved in the beginning. Some of the required
167
+ properties should be automatically filled but for those fields that are not,
168
+ define the following (optional):
169
+ * Sign on URL: https://your-organization.org/users/auth/msad
170
+ * Relay State: https://your-organization.org/
171
+ 8. Click "Save" and wait for the configurations to update.
172
+ 9. Click "Edit" under the "User Attributes & Claims" section and define the
173
+ attributes shown in the table below. Leave the "Unique User Identifier
174
+ (Name ID)" claim untouched unless you know what you are doing.
175
+ 10. Once done with the claims, copy the "App Federation Metadata Url" from the
176
+ "SAML Signing Certificate" section of the Single sign-on view and configure
177
+ it to the Decidim module's `:idp_metadata_url` configuration of your Decidim
178
+ instance as explained in this document. If you don't see the metadata URL
179
+ in this section, wait for a moment for the application to deploy at Azure.
180
+ 12. After the single sign-on configuration, decide if you want to assign users
181
+ manually to this application or let every user sign in to the application.
182
+ If you want to provide access to all users in your Azure AD, go to the
183
+ "Properties" tab of your Enterprise Application and change the "User
184
+ assignment required?" configuration to "No".
185
+ 13. Test that the integration is working correctly.
186
+
187
+ These are the attributes you will need to configure for the "User Attributes &
188
+ Claims" section (some of these should be already pre-defined by Azure):
189
+
190
+ | Name | NameSpace | Source | Source attribute |
191
+ | ------------ | ------------------------------------------------------ | --------- | ---------------------- |
192
+ | emailaddress | http​://schemas.xmlsoap.org/ws/2005/05/identity/claims | Attribute | user.mail |
193
+ | name | http​://schemas.xmlsoap.org/ws/2005/05/identity/claims | Attribute | user.userprincipalname |
194
+ | givenname | http​://schemas.xmlsoap.org/ws/2005/05/identity/claims | Attribute | user.givenname |
195
+ | surname | http​://schemas.xmlsoap.org/ws/2005/05/identity/claims | Attribute | user.surname |
196
+ | displayname | http​://schemas.microsoft.com/identity/claims | Attribute | user.displayname |
197
+
198
+ These are the minimum claims to be passed. You can also pass extra claims and
199
+ store them in the user's authorization metadata as explained in the
200
+ [Customization](#customization) section of this document. This may be useful
201
+ e.g. if you want to limit some sections of your service only to specific users
202
+ using Decidim's action authorizers.
203
+
204
+ ### Configuring ADFS
205
+
206
+ ADFS does not generally undestand the SAML metadata very well and it does not
207
+ provide any further information about what might be wrong with the SAML
208
+ metadata XML. Therefore, the metadata you will see in the Decidim's metadata URL
209
+ will most likely not work when trying to directly import it to ADFS unless you
210
+ figure out what is missing from it and add it using the `:sp_metadata`
211
+ configuration (if you figure it out, do let us know). This seems to be a rather
212
+ common problem with ADFS using the SAML based relying party trusts.
213
+
214
+ To configure ADFS, follow these steps:
215
+
216
+ 1. Open your Decidim instance's metadata url as specified above in order to get
217
+ the required data from it.
218
+ 2. On the ADFS server, open AD FS Management and select "Add Relying Party
219
+ Trust" under "Relying Party Trusts".
220
+ 3. For the data source, select "Enter data about the relying party manually".
221
+ 4. For the "Display name" set whatever your instance is called.
222
+ 5. For profile, select "AD FS Profile" which allows connecting with SAML.
223
+ 6. Bypass the ceritifcate configuration unless you have configured an SP
224
+ certificate in the module's configuration. Consult the ADFS administrator to
225
+ get further information about the organization's security policies regarding
226
+ this.
227
+ 7. In the URL configuration, select "Enable support for the SAML 2.0 WebSSO
228
+ protocol" and provide the following URL as the service URL (modified with
229
+ your instance's domain):
230
+ https://your-organization.org/users/auth/msad/callback
231
+ 8. In the relying party trust identifier section, add the following URL:
232
+ https://your-organization.org/users/auth/msad/metadata. Note that if you have
233
+ customized the SAML entity ID for your instance, use that insted. You should
234
+ see the correct value from your instance's metadata URL.
235
+ 9. In the final step, leave "Open the Edit Claim Rules dialog" checked and
236
+ proceed to configuring the claims.
237
+ 10. Click "Add Rule" and pick "Send LDAP Attributes as Claims" as the claim rule
238
+ template.
239
+ 11. Give the rule name "SAMLAttributes" and configure the values as shown in the
240
+ table below.
241
+ 12. Add a transform rule by selecting "Transform an Incoming Claim" as the claim
242
+ rule template. For the transform rule, define the following properties:
243
+ * Claim rule name: `NameID Transform`
244
+ * Incoming claim type: `Name ID`
245
+ * Outgoing claim type: `Name ID`
246
+ * Outgoing name ID format: `Email`
247
+ * Pass through all claim values
248
+ 13. Change the relying party trust secure hash algorithm to SHA-256 under the
249
+ relying party's "Properties" window's "Advanced" tab.
250
+
251
+ These are the attributes you will need to configure for the "SAMLAttributes"
252
+ rule:
253
+
254
+ | LDAP Attribute | Outgoing Claim Type |
255
+ | ------------------- | -------------------------------------------------------- |
256
+ | E-Mail-Addresses | Name ID |
257
+ | E-Mail-Addresses | E-Mail Address |
258
+ | User-Principal-Name | Name |
259
+ | Given-Name | Given Name |
260
+ | Surname | Surname |
261
+ | DisplayName | http​://schemas.microsoft.com/identity/claims/displayname |
262
+
263
+ The claims should be passed back to Decidim with the following schema names:
264
+
265
+ - E-Mail Address: http​://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
266
+ - Name: http​://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
267
+ - Given Name: http​://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
268
+ - Surname: http​://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
269
+ - DisplayName: http​://schemas.microsoft.com/identity/claims/displayname
270
+
271
+ If this is not the case for a reason or another, adjust the "Outgoing Claim
272
+ Type" to match these values.
273
+
274
+ These are the minimum claims to be passed. You can also pass extra claims and
275
+ store them in the user's authorization metadata as explained in the
276
+ [Customization](#customization) section of this document. This may be useful
277
+ e.g. if you want to limit some sections of your service only to specific users
278
+ using Decidim's action authorizers.
279
+
280
+ After the ADFS side is configured, change the `:idp_metadata_url` to match the
281
+ ADFS server's metadata URL if you haven't configured it yet. The format of the
282
+ URL should look similar to this:
283
+
284
+ https://adfs.your-organization.org/FederationMetadata/2007-06/FederationMetadata.xml
285
+
286
+ #### Debugging the SAML responses
287
+
288
+ If your ADFS integration is not working properly and you will get login errors,
289
+ the first thing to check is the SAML response data passed from the ADFS server.
290
+ To do this, temporarily enable POST data logging on your server in order to
291
+ inspect the SAML responses from the ADFS server. Once you see the responses in
292
+ your logs, you can convert them to human readable XML using the following Ruby
293
+ code:
294
+
295
+ ```ruby
296
+ require "ruby-saml"
297
+
298
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
299
+ settings = idp_metadata_parser.parse_remote("IDP_METADATA_URL_HERE")
300
+ raw_response = CGI.unescape("ENCODED_SAMLRESPONSE_POST_DATA")
301
+ response = OneLogin::RubySaml::Response.new(raw_response, { settings: settings })
302
+ puts response.document.to_s.inspect
303
+ ```
304
+
305
+ This will print the SAMLResponse in human readable XML for further inspection.
306
+
307
+ #### Error: "Authentication failed or cancelled. Please try again."
308
+
309
+ If you see the SAML StatusCode
310
+ `urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy` in the SAML response
311
+ data, it can mean that the ADFS server is not passing the SPNameQualifier value
312
+ as a claim back to the Decidim instance. In order to fix this, add a new claim
313
+ rule and select "Send Claims Using a Custom Rule". Give the rule name
314
+ "SPNameQualifier" and define the following in the "Custom rule" field:
315
+
316
+ ```
317
+ c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
318
+ => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/spnamequalifier"] = "YOUR_ENTITY_ID_HERE");
319
+ ```
320
+
321
+ In the rule, replace `YOUR_ENTITY_ID_HERE` with the entity ID of your instance
322
+ which should be `https://your-organization.org/users/auth/msad/metadata` with
323
+ the default configurations.
324
+
325
+ Alternatively, you could also try to configure a different NameID format from
326
+ the ADFS side and specify the format for this module using the following
327
+ configuration in the initializer:
328
+
329
+ ```ruby
330
+ # config/initializers/msad.rb
331
+
332
+ Decidim::Msad.configure do |config|
333
+ # ... keep the default configuration as is ...
334
+ # Add this extra configuration:
335
+ config.extra = {
336
+ # Specify the actual name identifier format the ADFS server is set to serve.
337
+ # Should be one of the following:
338
+ # - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
339
+ # - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
340
+ # - urn:oasis:names:tc:SAML:2.0:nameid-format:transient
341
+ # - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
342
+ name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
343
+ }
344
+ end
345
+ ```
346
+
347
+ Note that the NameID field you configure at the ADFS side is used to uniquely
348
+ identify the users, so it should be unique per user and at least somewhat
349
+ permanent for all users.
350
+
351
+ #### Error: Sign in attempt failed through MSAD because "Invalid ticket".
352
+
353
+ In case you see an "Invalid ticket" error durign your login, make sure that you
354
+ have gone through the previous "Authentication failed or cancelled". After this,
355
+ check that your entity ID is correct in the custom rule and matches the entity
356
+ ID you see in the metadata.
357
+
358
+ ## Usage
359
+
360
+ After the installation and configuration steps, you will need to enable the
361
+ Active Directory sign in method and authorization from Decidim's system
362
+ management panel. After enabled, you can start using it.
363
+
364
+ The Active Directory sign in method shipped with this gem will automatically
365
+ authorize the user accounts that signed in through AD. In case the users already
366
+ have an account, they can still authorize their existing accounts using the
367
+ Active Directory authorization if they want to avoid generating multiple user
368
+ accounts. This happens from the authorizations section of the user profile
369
+ pages.
370
+
371
+ ## Customization
372
+
373
+ For some specific needs, you may need to store extra metadata for the Active
374
+ Directory authorization or add new authorization configuration options for the
375
+ authorization.
376
+
377
+ This can be achieved by applying the following configuration to the module
378
+ inside the initializer described above:
379
+
380
+ ```ruby
381
+ # config/initializers/msad.rb
382
+
383
+ Decidim::Msad.configure do |config|
384
+ # ... keep the default configuration as is ...
385
+ # Add this extra configuration:
386
+ config.workflow_configurator = lambda do |workflow|
387
+ # When expiration is set to 0 minutes, it will never expire.
388
+ workflow.expires_in = 0.minutes
389
+ workflow.action_authorizer = "CustomMsadActionAuthorizer"
390
+ workflow.options do |options|
391
+ options.attribute :custom_option, type: :string, required: false
392
+ end
393
+ end
394
+ config.metadata_collector_class = CustomMsadMetadataCollector
395
+ end
396
+ ```
397
+
398
+ For the workflow configuration options, please refer to the
399
+ [decidim-verifications documentation](https://github.com/decidim/decidim/tree/master/decidim-verifications).
400
+
401
+ For the custom metadata collector, please extend the default class as follows:
402
+
403
+ ```ruby
404
+ # frozen_string_literal: true
405
+
406
+ class CustomMsadMetadataCollector < Decidim::Msad::Verification::MetadataCollector
407
+ def metadata
408
+ super.tap do |data|
409
+ # You can access the SAML attributes using the `saml_attributes` accessor
410
+ # which is an instance of `OneLogin::RubySaml::Attributes`. It has the
411
+ # instance methods `#single` for fetching single attributes and `#multi`
412
+ # for fetching attributes that have multiple values.
413
+ computer_model = saml_attributes.single("computer_model")
414
+ unless computer_model.blank?
415
+ # This will actually add the data to the user's authorization metadata
416
+ # hash.
417
+ data[:computer] = "Model: #{computer_model}"
418
+ end
419
+ end
420
+ end
421
+ end
422
+ ```
423
+
424
+ Please note that if you don't need to do very customized metadata collection,
425
+ customizing the metadata collector should not be generally necessary. Instead,
426
+ you can use the `metadata_attributes` configuration option which allows you to
427
+ define the SAML attribute keys and their associated metadata keys to be stored
428
+ with the user's authorization. Customization of the metadata collector is only
429
+ necessary in cases where you need to calculate new values or process the
430
+ original values somehow prior to saving them to the user's metadata.
431
+
432
+ ## Contributing
433
+
434
+ See [Decidim](https://github.com/decidim/decidim).
435
+
436
+ ### Testing
437
+
438
+ To run the tests run the following in the gem development path:
439
+
440
+ ```bash
441
+ $ bundle
442
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rake test_app
443
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rspec
444
+ ```
445
+
446
+ Note that the database user has to have rights to create and drop a database in
447
+ order to create the dummy test app database.
448
+
449
+ In case you are using [rbenv](https://github.com/rbenv/rbenv) and have the
450
+ [rbenv-vars](https://github.com/rbenv/rbenv-vars) plugin installed for it, you
451
+ can add these environment variables to the root directory of the project in a
452
+ file named `.rbenv-vars`. In this case, you can omit defining these in the
453
+ commands shown above.
454
+
455
+ ### Test code coverage
456
+
457
+ If you want to generate the code coverage report for the tests, you can use
458
+ the `SIMPLECOV=1` environment variable in the rspec command as follows:
459
+
460
+ ```bash
461
+ $ SIMPLECOV=1 bundle exec rspec
462
+ ```
463
+
464
+ This will generate a folder named `coverage` in the project root which contains
465
+ the code coverage report.
466
+
467
+ ### Localization
468
+
469
+ If you would like to see this module in your own language, you can help with its
470
+ translation at Crowdin:
471
+
472
+ https://crowdin.com/project/decidim-msad
473
+
474
+ ## License
475
+
476
+ See [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt).