decidim-msad 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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).