diff --git a/docs/redirects.json b/docs/redirects.json index 596e28a..86a1882 100644 --- a/docs/redirects.json +++ b/docs/redirects.json @@ -2837,6 +2837,9 @@ "services-firefly-iii-options-shb.firefly-iii.importer": [ "services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer" ], + "services-firefly-iii-options-shb.firefly-iii.importer.enable": [ + "services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.enable" + ], "services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken": [ "services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken" ], diff --git a/modules/services/firefly-iii.nix b/modules/services/firefly-iii.nix index 6388e95..7a6f2b7 100644 --- a/modules/services/firefly-iii.nix +++ b/modules/services/firefly-iii.nix @@ -261,6 +261,10 @@ in default = { }; type = lib.types.submodule { options = { + enable = lib.mkEnableOption "Firefly-iii Data Importer." // { + default = true; + }; + subdomain = lib.mkOption { type = lib.types.str; description = '' @@ -348,7 +352,7 @@ in } ]; } - { + (lib.mkIf cfg.importer.enable { services.firefly-iii-data-importer = { enable = true; @@ -370,7 +374,7 @@ in subdomain = cfg.importer.subdomain; } ]; - } + }) (lib.mkIf (cfg.smtp != null) { services.firefly-iii.settings = { MAIL_MAILER = "smtp"; @@ -405,6 +409,18 @@ in policy = cfg.sso.authorization_policy; subject = [ "group:${cfg.ldap.userGroup}" ]; } + ]; + } + ]; + }) + (lib.mkIf (cfg.sso.enable && cfg.importer.enable) { + shb.nginx.vhosts = [ + { + inherit (cfg.importer) subdomain; + inherit (cfg) domain ssl; + inherit (cfg.sso) authEndpoint; + + autheliaRules = [ { domain = "${cfg.importer.subdomain}.${cfg.domain}"; policy = cfg.sso.authorization_policy; diff --git a/test/services/firefly-iii.nix b/test/services/firefly-iii.nix index 3a271f6..caae9eb 100644 --- a/test/services/firefly-iii.nix +++ b/test/services/firefly-iii.nix @@ -6,6 +6,7 @@ let { ... }: [ "phpfpm-firefly-iii.service" + "phpfpm-firefly-iii-data-importer.service" "nginx.service" ]; waitForPorts = @@ -35,6 +36,7 @@ let siteOwnerEmail = "mail@example.com"; appKey.result = config.shb.hardcodedsecret.appKey.result; dbPassword.result = config.shb.hardcodedsecret.dbPassword.result; + importer.firefly-iii-accessToken.result = config.shb.hardcodedsecret.accessToken.result; }; # systemd.tmpfiles.rules = [ @@ -55,6 +57,10 @@ let request = config.shb.firefly-iii.dbPassword.request; settings.content = pkgs.lib.strings.replicate 64 "Y"; }; + shb.hardcodedsecret.accessToken = { + request = config.shb.firefly-iii.importer.firefly-iii-accessToken.request; + settings.content = pkgs.lib.strings.replicate 64 "X"; + }; }; clientLogin = @@ -83,6 +89,33 @@ let }; }; + clientLoginDataImporter = + { config, ... }: + { + imports = [ + shb.test.baseModule + shb.test.clientLoginModule + ]; + test = { + subdomain = "f-importer"; + }; + + test.login = { + startUrl = "http://${config.test.fqdn}"; + # There is no login without SSO integration. + testLoginWith = [ + { + username = null; + password = null; + nextPageExpect = [ + # The error to connect is expected since the access token must be created manually in Firefly-iii. + "expect(page.get_by_text('The importer could not connect')).to_be_visible()" + ]; + } + ]; + }; + }; + https = { config, ... }: { @@ -97,6 +130,7 @@ let shb.firefly-iii = { ldap = { userGroup = "user_group"; + adminGroup = "admin_group"; }; }; }; @@ -168,6 +202,75 @@ let }; }; + clientLoginSsoDataImporter = + { config, ... }: + { + imports = [ + shb.test.baseModule + shb.test.clientLoginModule + ]; + test = { + subdomain = "f-importer"; + }; + + test.login = { + startUrl = "https://${config.test.fqdn}"; + usernameFieldLabelRegex = "Username"; + passwordFieldLabelRegex = "Password"; + loginButtonNameRegex = "[sS]ign [iI]n"; + testLoginWith = [ + { + username = "alice"; + password = "NotAlicePassword"; + nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()" + ]; + } + { + username = "alice"; + password = "AlicePassword"; + nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()" + "expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()" + # Only admins have access + "expect(page.get_by_text('Authenticated')).to_be_visible(timeout=10000)" + ]; + } + { + username = "bob"; + password = "NotBobPassword"; + nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()" + ]; + } + { + username = "bob"; + password = "BobPassword"; + nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()" + "expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()" + # The error to connect is expected since the access token must be created manually in Firefly-iii. + "expect(page.get_by_text('The importer could not connect')).to_be_visible(timeout=10000)" + ]; + } + { + username = "charlie"; + password = "NotCharliePassword"; + nextPageExpect = [ + "expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()" + ]; + } + { + username = "charlie"; + password = "CharliePassword"; + nextPageExpect = [ + "expect(page).to_have_url(re.compile('.*/authenticated'))" + ]; + } + ]; + }; + }; + sso = { config, ... }: { @@ -197,6 +300,23 @@ in testScript = commonTestScript.access; }; + data-importer_basic = shb.test.runNixOSTest { + name = "firefly-iii-data-importer_basic"; + + nodes.client = { + imports = [ + clientLoginDataImporter + ]; + }; + nodes.server = { + imports = [ + basic + ]; + }; + + testScript = commonTestScript.access; + }; + backup = shb.test.runNixOSTest { name = "firefly-iii_backup"; @@ -259,4 +379,29 @@ in redirectSSO = true; }; }; + + data-importer_sso = shb.test.runNixOSTest { + name = "firefly-iii-data-importer_sso"; + + nodes.client = { + imports = [ + clientLoginSsoDataImporter + ]; + }; + nodes.server = + { config, pkgs, ... }: + { + imports = [ + basic + shb.test.certs + https + shb.test.ldap + ldap + (shb.test.sso config.shb.certs.certs.selfsigned.n) + sso + ]; + }; + + testScript = commonTestScript.access; + }; }