Commit Graph

1427 Commits

Author SHA1 Message Date
Javi Martín
efc46fe6c8 Add Performance/StringIdentifierArgument rule
It was added in rubocop-performance 1.13.0. We were already applying it
in most places.

We aren't adding it for performance reasons but in order to make the
code more consistent.
2023-01-11 16:05:20 +01:00
Javi Martín
25435b0297 Make it possible to disable tenants
Note we could use `acts_as_paranoid` with the `without_default_scope`
option, but we aren't doing so because it isn't possible to consider
deleted records in uniqueness validations with the paranoia gem [1].
I've added tests for these cases so we don't accidentally add
`acts_as_paranoid` in the future.

Also note we're extracting a `RowComponent` because, when
enabling/disabling a tenant, we're also enabling/disabling the link
pointing to its URL, and so we need to update the URL column after the
AJAX call.

[1] See issues 285 and 319 in https://github.com/rubysherpas/paranoia/
2022-12-28 14:34:00 +01:00
Javi Martín
0dac6ead77 Raise an exception when accessing a domain as subdomain
We were returning `nil` in this case, meaning we would be accessing the
main tenant in this situation instead of returning "Not Found".
2022-12-28 14:34:00 +01:00
Javi Martín
e1e16d21c3 Allow having tenants with different domains
Some institutions using CONSUL have expressed interest in this feature
since some of their tenants might already have their own domains.

We've considered many options for the user interface to select whether
we're using a subdomain or a domain, like having two separate fields,
using a check box, ... In the end we've chosen radio buttons because
they make it easier to follow a logical sequence: first you decide
whether you're introducing a domain or subdomain, and then you enter it.

We've also considered hiding this option and assuming "if it's got a
dot, it's a domain". However, this wouldn't work with nested subdomains
and it wouldn't work with domains which are simply machine names.

Note that a group of radio buttons (or check boxes) is difficult to
style when the text of the label might expand over more than one line
(as is the case here on small screens); in this case, most solutions
result in the second line of the label appearing immediately under the
radio button, instead of being aligned with the first line of the label.
That's why I've added a container for the input+label combination.
2022-12-13 13:10:02 +01:00
Javi Martín
e9c2776252 Don't create schemas in model tests unless needed
Creating a schema takes about 3-4 seconds on my machine, so omitting
the callbacks makes tests much faster.

To do so, we're using the `insert!` method added in Rails 6.0, which
inserts a record without executing callbacks or validations. To make the
tests look consistent, we're adding a FactoryBot strategy which uses
`insert!` instead of `create!`.

Note this strategy is useless in most cases because it doesn't work when
models have translatable attributes or associations. However, IMHO it's
worth it even if we only use it for tenants.

We could also use `Tenant.insert!` instead, but then we would have to
add all the mandatory attributes, and in this case the code is clearer
if we only add the attributes we need for the test.
2022-12-13 13:10:02 +01:00
Javi Martín
061ae94f98 Use the tenant name as default organization name
This way we'll avoid having all tenant organizations named "CONSUL" by
default, and we'll also use different email senders per tenant, which
will reduce the change of the emails being marked as spam.
2022-11-28 16:42:41 +01:00
Javi Martín
03614325d1 Use current host as default email address domain
We were using `consul.dev` as default, which might be fine for the
development environment, but it's something that definitely needs to be
changed on production.

Now we're providing a better default setting: using the same domain
which is used to generate URLs in emails. This also makes it easier to
configure new tenants, since the setting will default to whatever host
the new tenant is using.

The `|| "consul.dev"` part might not be needed; I'm keeping it because
I'm not sure production environents would not experience any new issues
if we don't add it, but we might remove it in the future.
2022-11-28 16:39:09 +01:00
Javi Martín
edd47877c2 Extract methods to get tenant subfolder/file paths
We were using the same logic in many different places, so we're
simplifying the code. I'm not convinced about the method names, though,
so we might change them in the future.

Note using this method for the default tenant in the `TenantDiskService`
class resulted in a `//` in the path, which is probably harmless but
very ugly and it also generates a different key than the one we got
until now. I've added an extra test to make sure that isn't the case.
2022-11-11 01:41:14 +01:00
Javi Martín
2f312bf474 Use a different machine learning folder per tenant
We're using the "tenants" subfolder for consistency with the folder
structure we use in ActiveStorage and because some CONSUL installations
might have folders inside the `data` folder which might conflict with
the folders created by tenants.

Note that the Python scripts have a lot of duplication, meaning we need
to change all of them. I'm not refactoring them because I'm not familiar
enough with these scripts (or with Python, for that matter).

Also note that the scripts folder is still shared by all tenants,
meaning it isn't possible to have different scripts for different
tenants. I'm not sure how this situation should be handled; again, I'm
not familiar enough with this feature.
2022-11-11 01:40:04 +01:00
Javi Martín
06d0c26126 Allow having different SMTP settings per tenant
Right now this is configured using the `secrets.yml` file, which is the
file we've used in the past to configure SMTP settings.

Note that, in the `current_secrets` method, the `if default?` condition
is there so in single-tenant applications it returns the exact same
object as `Rails.application.secrets`, and it makes it immediately clear
for developers reading the code. We're also caching the tenant secrets
(using `||=`) so they behave the same way as Rails secrets; for this to
work properly 100% of the time (for example, in tests) we need to expire
these cached secrets whenever the Rails secrets change.

A similar `unless Tenant.default?` condition is present in the
ApplicationMailer because there's a chance some CONSUL installations
might not be using secrets to define the SMTP settings(they might be
using environment variables, for example) and so in this case we don't
want to force settings based on the secrets.yml file because it would
break the application.

The structure of the SMTP settings in the secrets file should be:

```
production:
  tenants:
    name_of_the_tenant_subdomain:
      smtp_settings:
        address:
        (...)
```
2022-11-11 01:39:29 +01:00
Javi Martín
314ce70000 Use a different attachments folder per tenant
While this is not strictly necessary, it can help moving the data of one
tenant to a different server or removing it.

Note we're using subfolders inside the `tenants` subfolder. If we simply
used subfolders with the schema names, if the schema names were, for
instance, language codes like `es`, `en`, `it`, ... they would conflict
with the default subfolders used by Active Storage.
2022-11-11 01:39:19 +01:00
Javi Martín
a71f4d87f8 Add an interface to manage tenants
Note we aren't allowing to delete a tenant because it would delete all
its data, so this action is a very dangerous one. We might need to add a
warning when creating a tenant, indicating the tenant cannot be
destroyed. We can also add an action to delete a tenant which forces the
admin to write the name of the tenant before deleting it and with a big
warning about the danger of this operation.

For now, we're letting administrators of the "main" (default) tenant to
create other tenants. However, we're only allowing to manage tenants
when the multitenancy configuration option is enabled. This way the
interface won't get in the way on single-tenant applications.

We've thought about creating a new role to manage tenants or a new URL
out of the admin area. We aren't doing so for simplicity purposes and
because we want to keep CONSUL working the same way it has for
single-tenant installations, but we might change it in the future.
There's also the fact that by default we create one user with a known
password, and if by default we create a new role and a new user to
handle tenants, the chances of people forgetting to change the password
of one of these users increases dramatically, particularly if they
aren't using multitenancy.
2022-11-09 18:19:20 +01:00
Javi Martín
d904fe8b4f Move subdomain logic to tenant model
We had some of the logic in the ApplicationMailer. Since we're going to
use this logic in more places, we're moving it to the Tenant model,
which is the model handling everything related to hosts.
2022-11-09 18:19:20 +01:00
Javi Martín
9759288f3b Run DB rake tasks on all tenants
Some tasks don't have to run on every tenant. The task to calculate the
TSV is only done for records which were present before we added the TSV
column, and that isn't going to happen in any tenants because we added
the TSV column before adding the tenants table. Similarly, the migration
needed for existing polls isn't necessary because there weren't any
tenants before we allowed to set the starting/ending time to polls.

We aren't adding any tests for these tasks because tests for rake tasks
are slow and tests creating tenants are also slow, making the
combination of the two even slower, particularly if we add tests for
every single task we're changing. We're adding tests for the
`.run_on_each` method instead.
2022-11-09 18:19:20 +01:00
Javi Martín
5983006657 Use a custom method to detect the current tenant
The subdomain elevator we were using, which is included in apartment,
didn't work on hosts already including a subdomain (like
demo.consul.dev, for instance). In those cases, we would manually add
the subdomain to the list of excluded subdomains. Since these subdomains
will be different for different CONSUL installations, it meant each
installation had to customize the code. Furthermore, existing
installations using subdomains would stop working.

So we're using a custom method to find the current tenant, based on the
host defined in `default_url_options`.

In order to avoid any side-effects on single-tenant applications, we're
adding a new configuration option to enable multitenancy

We're enabling two ways to handle this configuration option:

a) Change the application_custom.rb file, which is under version control
b) Change the secrets.yml file, which is not under version control

This way people prefering to handle configuration options through
version control can do so, while people who prefer handling
configuration options through te secrets.yml file can do so as well.

We're also disabling the super-annoying warnings mentioning there are no
tenants which we got every time we run migrations on single-tenant
applications. These messages will only be enabled when the multitenancy
feature is enabled too. For this reason, we're also disabling the
multitenancy feature in the development environment by default.
2022-11-09 18:19:20 +01:00
Javi Martín
d77cf77761 Validate format of subdomains / schema names
Note we're using the `:HOST` regular expression since subdomains can
contain the same characters as domains do. This isn't 100% precise,
though, since subdomains have a maximum length of 63 characters, but is
good enough for our purposes.
2022-11-09 18:19:01 +01:00
Javi Martín
c483c6036a Install extensions in a shared schema
This way all tenants will be able to access them instead of just the
default one.

The apartment gem recommends using a rake task instead of a migration,
but that's a solution which is primarily meant for new installations.
Migrations are easier to execute on existing installations.

However, since this migration doesn't affect the `schema.rb` file, we
still need to make sure the shared schema is created in tasks which do
not execute migrations, like `db:schema:load` or `db:test:prepare`, just
like the apartment gem recommends. That's why we're enhancing these
tasks so they execute this migration.

Note that there might be cases where the database user isn't a superuser
(as it's usually the case on production environments), meaning commands
to create, alter or drop extensions will fail. There's also the case
where users don't have permissions to create schemas, which is needed in
order to create the shared extensions schema and the schemas used by the
tenants. For these reasons, we're minimizing the number of commands, and
so we only alter or create extensions when it is really necessary.

When users don't have permission, we aren't running the commands but
showing a warning with the steps needed to run the migration manually.
This is only necessary on installations which are going to use
multitenancy; single-tenant applications upgrading don't need to run
this migration, and that's why we aren't raising exceptions when we
can't run it.

For new installations, we'll change the CONSUL installer so extensions
are automatically created in the shared schema.

Also note the plpgsql extension is not handled here. This is a special
extension which must be installed on the pg_catalog schema, which is
always in the search path and so is shared by all tenants.

Finally, we also need to change the `database.yml` file in order to
search for shared extensions while running migrations or model tests,
since none of our enabled extensions are executed during migrations;
we're also adding a rake task for existing installations. Quoting the
apartment documentation:

> your database.yml file must mimic what you've set for your default and
> persistent schemas in Apartment. When you run migrations with Rails,
> it won't know about the extensions schema because Apartment isn't
> injected into the default connection, it's done on a per-request
> basis.
2022-11-09 17:53:31 +01:00
Eduardo Vilar
382abb3666 Add multitenancy with apartment
Co-Authored-By: Javi Martín <javim@elretirao.net>
2022-11-09 17:52:05 +01:00
Javi Martín
33f7a05d3e Fix crash running machine learning with no data dir
The scripts crashed when the `data` folder wasn't present, which is the
common situation in development environments or production environments
not using Capistrano, since this folder isn't under version control.
2022-10-19 03:02:56 +02:00
Senén Rodero Rodríguez
3da4112d94 Remove Poll::Voter record when there is no more user answers
Now we can remove answers we should provide a way of removing voting.
2022-10-18 11:04:40 +02:00
Senén Rodero Rodríguez
7df0e9a961 Allow to remove poll answers 2022-10-18 11:04:40 +02:00
decabeza
48d7ec75d0 Do not allow to create more answers than the maximum defined
In case we receive consecutive requests we are locking the poll author record
until the first request transaction ends, so the author answers count during
subsequent requests validations is up to date.
2022-10-18 10:39:00 +02:00
decabeza
4fba76f64f Do not show answers without additional information 2022-10-18 10:38:59 +02:00
decabeza
d1c1aa6691 Add VotationType model 2022-10-18 10:38:59 +02:00
Senén Rodero Rodríguez
64676be246 Remove token column from poll_voters table
As it is no longer used as originally pretended [1][2].

[1] Check consul/consul pull request 1994
[2] Check consul/consul pull request 3539
2022-09-22 10:34:07 +02:00
Julian Herrero
4c8f247de7 Don't allow to modify answer's documents for started polls 2022-09-20 17:50:49 +02:00
Julian Herrero
245594f32b Don't allow to modify answer's images for started polls
Note that the `create` action doesn't create an image but updates an
answer instead. We're removing the references to `:create` in the
abilities since it isn't used.

In the future we might change the form to add an image to an answer
because it's been broken for ages since it shows all the attached
images.
2022-09-20 17:50:49 +02:00
Julian Herrero
5fe86264ca Don't allow to modify answer's videos for started polls
Same rules that will apply for the answer itself should apply for the
attachments like videos, images, and/or documents.
2022-09-20 17:50:49 +02:00
Julian Herrero
14542df0de Allow to delete answers if the poll has not started yet
Deleting answers was not even possible. But it was possible to delete
questions. So we implemented the same behavior.
2022-09-20 17:50:49 +02:00
Julian Herrero
3a6e99cb8c Don't allow changing answers if the poll has started
Just like we did with questions.
2022-09-20 17:50:49 +02:00
Julian Herrero
8a26954bc5 Don't allow to modify questions for started polls
Adding, modifiying, and/or deleting questions for an already started
poll is far away from being democratic and can lead to unwanted side
effects like missing votes in the results or stats.

So, from now on, only modifiying questions will be possible only if
the poll has not started yet.
2022-09-20 17:50:35 +02:00
Julian Herrero
ecd22131f1 Disallow to modify the end date for an ended poll 2022-09-20 17:29:01 +02:00
Julian Herrero
8b5c315eb0 Allow to modify the end date as long as it is not to the past 2022-09-20 17:21:39 +02:00
Julian Herrero
471096c698 Disallow to modify the start date for an already started poll
We need to update a couple of tests because a poll is created in the
tests with a timestamp that includes nanoseconds and in the form to edit
the time of the poll the nanoseconds are not sent, meaning it was
detected as a change.
2022-09-20 17:21:36 +02:00
Julian Herrero
855fedc025 Allow only creation of polls with dates that are not in the past 2022-09-20 13:37:56 +02:00
taitus
d0359d584e Remove trait :current for poll on factories
When we create a poll with "create(:poll)" it is already a current poll.
2022-09-14 15:14:23 +02:00
Javi Martín
5a0fde4048 Allow selecting the time when a poll starts/ends
We were already saving it as a time, but we didn't offer an interface to
select the time due to lack of decent browser support for this field
back when this feature was added.

However, nowadays all major browsers support this field type and, at the
time of writing, at least 86.5% of the browsers support it [1]. This
percentage could be much higher, since support in 11.25% of the browsers
is unknown.

Note we still need to support the case where this field isn't supported,
and so we offer a fallback and on the server side we don't assume we're
always getting a time. We're doing a strange hack so we set the field
type to text before changing its value; otherwise old Firefox browsers
crashed.

Also note that, until now, we were storing end dates in the database as
a date with 00:00 as its time, but we were considering the poll to be
open until 23:59 that day. So, in order to keep backwards compatibility,
we're adding a task to update the dates of existing polls so we get the
same behavior we had until now.

This also means budget polls are now created so they end at the
beginning of the day when the balloting phase ends. This is consistent
with the dates we display in the budget phases table.

Finally, there's one test where we're using `beginning_of_minute` when
creating a poll. That's because Chrome provides an interface to enter a
time in a `%H:%M` format when the "seconds" value of the provided time
is zero. However, when the "seconds" value isn't zero, Chrome provides
an interface to enter a time in a `%H:%M:%S` format. Since Capybara
doesn't enter the seconds when using `fill_in` with a time, the test
failed when Capybara tried to enter a time in the `%H:%M` format when
Chrome expected a time in the `%H:%M:%S` format.

To solve this last point, an alternative would be to manually provide
the format when using `fill_in` so it includes the seconds.

[1] https://caniuse.com/mdn-html_elements_input_type_datetime-local
2022-09-14 15:14:23 +02:00
Javi Martín
3543767951 Don't include current polls in recounting polls
On the day when a poll ends, it was considered both current and
recounting.
2022-09-14 15:14:23 +02:00
Javi Martín
2153bd9e98 Add tests for current/expired/recounting polls
We didn't have model tests for some of these scopes, and neither scope
tests nor instance method tests covered edge cases like the current
date.
2022-09-14 15:14:23 +02:00
Javi Martín
bd85a09d32 Group poll scopes tests together
We're also using the convention of using a `.` to describe class
methods.
2022-09-14 15:14:23 +02:00
taitus
83e0d4c6b7 Add load_and_authorize_resource to images controller 2022-09-14 14:45:37 +02:00
taitus
99495a21d2 Add load_and_authorize_resource to videos controller 2022-09-14 14:45:36 +02:00
taitus
cb2958e1b0 Add load_and_authorize_resource to answers controller 2022-09-14 14:45:34 +02:00
Javi Martín
3a7a5b2346 Merge pull request #4920 from consul/budget_results_tests
Fix flaky budget results tests
2022-08-25 15:32:39 +02:00
Javi Martín
969e0014e5 Move budget result test to the model
We were testing that the `calculate_winners` method does not take the
price into account; we can do the same in a model test. We already have
a different system test to check that the price isn't displayed in the
view.
2022-08-25 14:31:51 +02:00
Javi Martín
c8270d58bd Add and apply Rails/DurationArithmetic rubocop rule
This rule was added in rubocop-rails 2.13.0. We were already applying it
most of the time.
2022-08-24 23:24:36 +02:00
Javi Martín
5b844bf231 Use index_with to simplify hash generation
This method was introduced in Rails 6.0. It can be used to take an array
and create a hash where the elements of the array are the indexes of the
hash.
2022-08-24 18:19:02 +02:00
Javi Martín
c97e7852a4 Use "excluding" instead of "reject"
This method was already available as #without, but we didn't know about
it.
2022-08-24 18:11:56 +02:00
Javi Martín
b59e2b31d3 Use unfreeze_time instead of travel_back
This is consistent with `with_frozen_time`.
2022-08-24 18:11:56 +02:00
Jacek Skrzypacz
2af7e32415 Add search form for hidden content
Added search for comments and proposal_notifications, added tsv column
for search and rake tasks to update/create tsv vector.
2022-08-23 14:30:38 +02:00