diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97f96ffc8..f8711f299 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -173,6 +173,7 @@ amd64: script: &release - mix deps.get --only prod - mkdir release + - export PLEROMA_BUILD_BRANCH=$CI_COMMIT_REF_NAME - mix release --path release diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7e5c9a1..d3c554245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,8 +62,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MRF: Support for running subchains. - Configuration: `skip_thread_containment` option - Configuration: `rate_limit` option. See `Pleroma.Plugs.RateLimiter` documentation for details. +- MRF: Support for filtering out likely spam messages by rejecting posts from new users that contain links. ### Changed +- **Breaking:** bind to 127.0.0.1 instead of 0.0.0.0 by default - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer - Thread containment / test for complete visibility will be skipped by default. - Enforcement of OAuth scopes diff --git a/config/config.exs b/config/config.exs index 3962ac019..0d07fc692 100644 --- a/config/config.exs +++ b/config/config.exs @@ -139,6 +139,7 @@ instrumenters: [Pleroma.Web.Endpoint.Instrumenter], url: [host: "localhost"], http: [ + ip: {127, 0, 0, 1}, dispatch: [ {:_, [ diff --git a/config/dev.exs b/config/dev.exs index 71b11f7c3..7e1e3b4be 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -60,5 +60,5 @@ ) end -if File.exists?("./config/dev.migrated.secret.exs"), - do: import_config("./config/dev.migrated.secret.exs") +if File.exists?("./config/dev.exported_from_db.secret.exs"), + do: import_config("dev.exported_from_db.secret.exs") diff --git a/config/prod.exs b/config/prod.exs index 42edccf64..9c205cbd2 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -64,5 +64,5 @@ # which should be versioned separately. import_config "prod.secret.exs" -if File.exists?("./config/prod.migrated.secret.exs"), - do: import_config("./config/prod.migrated.secret.exs") +if File.exists?("./config/prod.exported_from_db.secret.exs"), + do: import_config("prod.exported_from_db.secret.exs") diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md index 5dcc8d059..c05a353d7 100644 --- a/docs/api/admin_api.md +++ b/docs/api/admin_api.md @@ -568,8 +568,9 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret { configs: [ { + "group": string, "key": string, - "value": string or {} or [] + "value": string or {} or [] or {"tuple": []} } ] } @@ -580,6 +581,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`. Atom or boolean value can be passed with `:` in the beginning, e.g. `":true"`, `":upload"`. Integer with `i:`, e.g. `"i:150"`. +Tuple with more than 2 values with `{"tuple": ["first_val", Pleroma.Module, []]}`. +`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`. Compile time settings (need instance reboot): - all settings by this keys: @@ -595,8 +598,9 @@ Compile time settings (need instance reboot): - Method `POST` - Params: - `configs` => [ + - `group` (string) - `key` (string) - - `value` (string, [], {}) + - `value` (string, [], {} or {"tuple": []}) - `delete` = true (optional, if parameter must be deleted) ] @@ -606,6 +610,7 @@ Compile time settings (need instance reboot): { configs: [ { + "group": "pleroma", "key": "Pleroma.Upload", "value": { "uploader": "Pleroma.Uploaders.Local", @@ -619,6 +624,9 @@ Compile time settings (need instance reboot): "follow_redirect": ":true", "pool": ":upload" } + }, + "dispatch": { + "tuple": ["/api/v1/streaming", "Pleroma.Web.MastodonAPI.WebsocketHandler", []] } } } @@ -631,8 +639,9 @@ Compile time settings (need instance reboot): { configs: [ { + "group": string, "key": string, - "value": string or {} or [] + "value": string or {} or [] or {"tuple": []} } ] } diff --git a/docs/clients.md b/docs/clients.md index dc3e83bcc..30358c210 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -49,13 +49,6 @@ Feel free to contact us to be added to this list! - Platforms: iOS, Android - Features: No Streaming -### Tootdon -- Homepage: , -- Source Code: ??? -- Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon) -- Platforms: Android, iOS -- Features: No Streaming - ### Tusky - Homepage: - Source Code: diff --git a/docs/config.md b/docs/config.md index ed8e465c6..b08c37e84 100644 --- a/docs/config.md +++ b/docs/config.md @@ -16,6 +16,13 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. ## Pleroma.Uploaders.Local * `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory +## Pleroma.Uploaders.S3 +* `bucket`: S3 bucket name +* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com") +* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc. +For example, when using CDN to S3 virtual host format, set "". +At this time, write CNAME to CDN in public_endpoint. + ## Pleroma.Upload.Filter.Mogrify * `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"impode", "1"}]`. @@ -90,6 +97,7 @@ config :pleroma, Pleroma.Emails.Mailer, * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (see ``:mrf_subchain`` section) * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section) * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. + * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. * `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send. * `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json`` diff --git a/lib/mix/tasks/pleroma/common.ex b/lib/mix/pleroma.ex similarity index 91% rename from lib/mix/tasks/pleroma/common.ex rename to lib/mix/pleroma.ex index 7d50605af..1b758ea33 100644 --- a/lib/mix/tasks/pleroma/common.ex +++ b/lib/mix/pleroma.ex @@ -2,19 +2,23 @@ # Copyright © 2017-2018 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Mix.Tasks.Pleroma.Common do +defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) {:ok, _} = Application.ensure_all_started(:pleroma) end + def load_pleroma do + Application.load(:pleroma) + end + def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do Keyword.get(options, opt) || shell_prompt(prompt, defval, defname) end def shell_prompt(prompt, defval \\ nil, defname \\ nil) do - prompt_message = "#{prompt} [#{defname || defval}]" + prompt_message = "#{prompt} [#{defname || defval}] " input = if mix_shell?(), diff --git a/lib/mix/tasks/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex similarity index 82% rename from lib/mix/tasks/benchmark.ex rename to lib/mix/tasks/pleroma/benchmark.ex index e4b1a638a..d43db7b35 100644 --- a/lib/mix/tasks/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -1,9 +1,9 @@ defmodule Mix.Tasks.Pleroma.Benchmark do + import Mix.Pleroma use Mix.Task - alias Mix.Tasks.Pleroma.Common def run(["search"]) do - Common.start_pleroma() + start_pleroma() Benchee.run(%{ "search" => fn -> @@ -13,7 +13,7 @@ def run(["search"]) do end def run(["tag"]) do - Common.start_pleroma() + start_pleroma() Benchee.run(%{ "tag" => fn -> diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 1fe03088d..4ed2c9789 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -1,6 +1,6 @@ defmodule Mix.Tasks.Pleroma.Config do use Mix.Task - alias Mix.Tasks.Pleroma.Common + import Mix.Pleroma alias Pleroma.Repo alias Pleroma.Web.AdminAPI.Config @shortdoc "Manages the location of the config" @@ -17,14 +17,14 @@ defmodule Mix.Tasks.Pleroma.Config do """ def run(["migrate_to_db"]) do - Common.start_pleroma() + start_pleroma() if Pleroma.Config.get([:instance, :dynamic_configuration]) do Application.get_all_env(:pleroma) |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end) |> Enum.each(fn {k, v} -> key = to_string(k) |> String.replace("Elixir.", "") - {:ok, _} = Config.update_or_create(%{key: key, value: v}) + {:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v}) Mix.shell().info("#{key} is migrated.") end) @@ -37,12 +37,13 @@ def run(["migrate_to_db"]) do end def run(["migrate_from_db", env]) do - Common.start_pleroma() + start_pleroma() if Pleroma.Config.get([:instance, :dynamic_configuration]) do - config_path = "config/#{env}.migrated.secret.exs" + config_path = "config/#{env}.exported_from_db.secret.exs" {:ok, file} = File.open(config_path, [:write]) + IO.write(file, "use Mix.Config\r\n") Repo.all(Config) |> Enum.each(fn config -> @@ -50,7 +51,9 @@ def run(["migrate_from_db", env]) do IO.write( file, - "config :pleroma, #{config.key}#{mark} #{inspect(Config.from_binary(config.value))}\r\n" + "config :#{config.group}, #{config.key}#{mark} #{ + inspect(Config.from_binary(config.value)) + }\r\n" ) {:ok, _} = Repo.delete(config) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 4d480ac3f..e91fb31d1 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -3,12 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Database do - alias Mix.Tasks.Pleroma.Common alias Pleroma.Conversation alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User require Logger + import Mix.Pleroma use Mix.Task @shortdoc "A collection of database related tasks" @@ -45,7 +45,7 @@ def run(["remove_embedded_objects" | args]) do ] ) - Common.start_pleroma() + start_pleroma() Logger.info("Removing embedded objects") Repo.query!( @@ -66,12 +66,12 @@ def run(["remove_embedded_objects" | args]) do end def run(["bump_all_conversations"]) do - Common.start_pleroma() + start_pleroma() Conversation.bump_for_all_activities() end def run(["update_users_following_followers_counts"]) do - Common.start_pleroma() + start_pleroma() users = Repo.all(User) Enum.each(users, &User.remove_duplicated_following/1) @@ -89,7 +89,7 @@ def run(["prune_objects" | args]) do ] ) - Common.start_pleroma() + start_pleroma() deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto/ecto.ex index af09cb289..324f57fdd 100644 --- a/lib/mix/tasks/pleroma/ecto/ecto.ex +++ b/lib/mix/tasks/pleroma/ecto/ecto.ex @@ -9,6 +9,15 @@ defmodule Mix.Tasks.Pleroma.Ecto do def ensure_migrations_path(repo, opts) do path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations") + path = + case Path.type(path) do + :relative -> + Path.join(Application.app_dir(:pleroma), path) + + :absolute -> + path + end + if not File.dir?(path) do raise_missing_migrations(Path.relative_to_cwd(path), repo) end @@ -22,7 +31,7 @@ def ensure_migrations_path(repo, opts) do def source_repo_priv(repo) do config = repo.config() priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" - Path.join(File.cwd!(), priv) + Path.join(Application.app_dir(:pleroma), priv) end defp raise_missing_migrations(path, repo) do diff --git a/lib/mix/tasks/pleroma/ecto/migrate.ex b/lib/mix/tasks/pleroma/ecto/migrate.ex index 22eafe76f..855c977f6 100644 --- a/lib/mix/tasks/pleroma/ecto/migrate.ex +++ b/lib/mix/tasks/pleroma/ecto/migrate.ex @@ -4,6 +4,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Migrate do use Mix.Task + import Mix.Pleroma require Logger @shortdoc "Wrapper on `ecto.migrate` task." @@ -37,6 +38,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Migrate do @impl true def run(args \\ []) do + load_pleroma() {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) opts = diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex index 0033ceba4..2ffb0901c 100644 --- a/lib/mix/tasks/pleroma/ecto/rollback.ex +++ b/lib/mix/tasks/pleroma/ecto/rollback.ex @@ -4,6 +4,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do use Mix.Task + import Mix.Pleroma require Logger @shortdoc "Wrapper on `ecto.rollback` task" @@ -36,6 +37,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do @impl true def run(args \\ []) do + load_pleroma() {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) opts = diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 44e49cb69..997eabbeb 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Pleroma.Instance do use Mix.Task - alias Mix.Tasks.Pleroma.Common + import Mix.Pleroma @shortdoc "Manages Pleroma instance" @moduledoc """ @@ -29,8 +29,11 @@ defmodule Mix.Tasks.Pleroma.Instance do - `--dbname DBNAME` - the name of the database to use - `--dbuser DBUSER` - the user (aka role) to use for the database connection - `--dbpass DBPASS` - the password to use for the database connection + - `--rum Y/N` - Whether to enable RUM indexes - `--indexable Y/N` - Allow/disallow indexing site by search engines - `--db-configurable Y/N` - Allow/disallow configuring instance from admin part + - `--uploads-dir` - the directory uploads go in when using a local uploader + - `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.) """ def run(["gen" | rest]) do @@ -49,8 +52,11 @@ def run(["gen" | rest]) do dbname: :string, dbuser: :string, dbpass: :string, + rum: :string, indexable: :string, - db_configurable: :string + db_configurable: :string, + uploads_dir: :string, + static_dir: :string ], aliases: [ o: :output, @@ -70,7 +76,7 @@ def run(["gen" | rest]) do if proceed? do [domain, port | _] = String.split( - Common.get_option( + get_option( options, :domain, "What domain will your instance use? (e.g pleroma.soykaf.com)" @@ -79,16 +85,16 @@ def run(["gen" | rest]) do ) ++ [443] name = - Common.get_option( + get_option( options, :instance_name, "What is the name of your instance? (e.g. Pleroma/Soykaf)" ) - email = Common.get_option(options, :admin_email, "What is your admin email address?") + email = get_option(options, :admin_email, "What is your admin email address?") notify_email = - Common.get_option( + get_option( options, :notify_email, "What email address do you want to use for sending email notifications?", @@ -96,7 +102,7 @@ def run(["gen" | rest]) do ) indexable = - Common.get_option( + get_option( options, :indexable, "Do you want search engines to index your site? (y/n)", @@ -104,21 +110,19 @@ def run(["gen" | rest]) do ) === "y" db_configurable? = - Common.get_option( + get_option( options, :db_configurable, - "Do you want to be able to configure instance from admin part? (y/n)", + "Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n)", "y" ) === "y" - dbhost = - Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost") + dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost") - dbname = - Common.get_option(options, :dbname, "What is the name of your database?", "pleroma_dev") + dbname = get_option(options, :dbname, "What is the name of your database?", "pleroma_dev") dbuser = - Common.get_option( + get_option( options, :dbuser, "What is the user used to connect to your database?", @@ -126,7 +130,7 @@ def run(["gen" | rest]) do ) dbpass = - Common.get_option( + get_option( options, :dbpass, "What is the password used to connect to your database?", @@ -134,13 +138,38 @@ def run(["gen" | rest]) do "autogenerated" ) + rum_enabled = + get_option( + options, + :rum, + "Would you like to use RUM indices?", + "n" + ) === "y" + + uploads_dir = + get_option( + options, + :upload_dir, + "What directory should media uploads go in (when using the local uploader)?", + Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads]) + ) + + static_dir = + get_option( + options, + :static_dir, + "What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?", + Pleroma.Config.get([:instance, :static_dir]) + ) + secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1) + template_dir = Application.app_dir(:pleroma, "priv") <> "/templates" result_config = EEx.eval_file( - "sample_config.eex" |> Path.expand(__DIR__), + template_dir <> "/sample_config.eex", domain: domain, port: port, email: email, @@ -150,47 +179,50 @@ def run(["gen" | rest]) do dbname: dbname, dbuser: dbuser, dbpass: dbpass, - version: Pleroma.Mixfile.project() |> Keyword.get(:version), secret: secret, signing_salt: signing_salt, web_push_public_key: Base.url_encode64(web_push_public_key, padding: false), web_push_private_key: Base.url_encode64(web_push_private_key, padding: false), - db_configurable?: db_configurable? + db_configurable?: db_configurable?, + static_dir: static_dir, + uploads_dir: uploads_dir, + rum_enabled: rum_enabled ) result_psql = EEx.eval_file( - "sample_psql.eex" |> Path.expand(__DIR__), + template_dir <> "/sample_psql.eex", dbname: dbname, dbuser: dbuser, - dbpass: dbpass + dbpass: dbpass, + rum_enabled: rum_enabled ) - Common.shell_info( + shell_info( "Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs." ) File.write(config_path, result_config) - Common.shell_info("Writing #{psql_path}.") + shell_info("Writing #{psql_path}.") File.write(psql_path, result_psql) - write_robots_txt(indexable) + write_robots_txt(indexable, template_dir) - Common.shell_info( + shell_info( "\n" <> """ To get started: 1. Verify the contents of the generated files. - 2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)}`. + 2. Run `sudo -u postgres psql -f #{escape_sh_path(psql_path)}`. """ <> if config_path in ["config/dev.secret.exs", "config/prod.secret.exs"] do "" else - "3. Run `mv #{Common.escape_sh_path(config_path)} 'config/prod.secret.exs'`." + "3. Run `mv #{escape_sh_path(config_path)} 'config/prod.secret.exs'`." end ) else - Common.shell_error( + shell_error( "The task would have overwritten the following files:\n" <> (Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <> "Rerun with `--force` to overwrite them." @@ -198,10 +230,10 @@ def run(["gen" | rest]) do end end - defp write_robots_txt(indexable) do + defp write_robots_txt(indexable, template_dir) do robots_txt = EEx.eval_file( - Path.expand("robots_txt.eex", __DIR__), + template_dir <> "/robots_txt.eex", indexable: indexable ) @@ -215,10 +247,10 @@ defp write_robots_txt(indexable) do if File.exists?(robots_txt_path) do File.cp!(robots_txt_path, "#{robots_txt_path}.bak") - Common.shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak") + shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak") end File.write(robots_txt_path, robots_txt) - Common.shell_info("Writing #{robots_txt_path}.") + shell_info("Writing #{robots_txt_path}.") end end diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index 213ae24d2..83ed0ed02 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Pleroma.Relay do use Mix.Task - alias Mix.Tasks.Pleroma.Common + import Mix.Pleroma alias Pleroma.Web.ActivityPub.Relay @shortdoc "Manages remote relays" @@ -24,24 +24,24 @@ defmodule Mix.Tasks.Pleroma.Relay do Example: ``mix pleroma.relay unfollow https://example.org/relay`` """ def run(["follow", target]) do - Common.start_pleroma() + start_pleroma() with {:ok, _activity} <- Relay.follow(target) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else - {:error, e} -> Common.shell_error("Error while following #{target}: #{inspect(e)}") + {:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}") end end def run(["unfollow", target]) do - Common.start_pleroma() + start_pleroma() with {:ok, _activity} <- Relay.unfollow(target) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else - {:error, e} -> Common.shell_error("Error while following #{target}: #{inspect(e)}") + {:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}") end end end diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex index 8855b5538..be45383ee 100644 --- a/lib/mix/tasks/pleroma/uploads.ex +++ b/lib/mix/tasks/pleroma/uploads.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do use Mix.Task - alias Mix.Tasks.Pleroma.Common + import Mix.Pleroma alias Pleroma.Upload alias Pleroma.Uploaders.Local require Logger @@ -24,7 +24,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do """ def run(["migrate_local", target_uploader | args]) do delete? = Enum.member?(args, "--delete") - Common.start_pleroma() + start_pleroma() local_path = Pleroma.Config.get!([Local, :uploads]) uploader = Module.concat(Pleroma.Uploaders, target_uploader) @@ -38,10 +38,10 @@ def run(["migrate_local", target_uploader | args]) do Pleroma.Config.put([Upload, :uploader], uploader) end - Common.shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}") + shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}") if delete? do - Common.shell_info( + shell_info( "Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)" ) @@ -78,7 +78,7 @@ def run(["migrate_local", target_uploader | args]) do |> Enum.filter(& &1) total_count = length(uploads) - Common.shell_info("Found #{total_count} uploads") + shell_info("Found #{total_count} uploads") uploads |> Task.async_stream( @@ -90,7 +90,7 @@ def run(["migrate_local", target_uploader | args]) do :ok error -> - Common.shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}") + shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}") end end, timeout: 150_000 @@ -99,10 +99,10 @@ def run(["migrate_local", target_uploader | args]) do # credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation |> Enum.reduce(0, fn done, count -> count = count + length(done) - Common.shell_info("Uploaded #{count}/#{total_count} files") + shell_info("Uploaded #{count}/#{total_count} files") count end) - Common.shell_info("Done!") + shell_info("Done!") end end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 7eaa49836..8a78b4fe6 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -5,9 +5,10 @@ defmodule Mix.Tasks.Pleroma.User do use Mix.Task import Ecto.Changeset - alias Mix.Tasks.Pleroma.Common + import Mix.Pleroma alias Pleroma.User alias Pleroma.UserInviteToken + alias Pleroma.Web.OAuth @shortdoc "Manages Pleroma users" @moduledoc """ @@ -49,6 +50,10 @@ defmodule Mix.Tasks.Pleroma.User do mix pleroma.user delete_activities NICKNAME + ## Sign user out from all applications (delete user's OAuth tokens and authorizations). + + mix pleroma.user sign_out NICKNAME + ## Deactivate or activate the user's account. mix pleroma.user toggle_activated NICKNAME @@ -115,7 +120,7 @@ def run(["new", nickname, email | rest]) do admin? = Keyword.get(options, :admin, false) assume_yes? = Keyword.get(options, :assume_yes, false) - Common.shell_info(""" + shell_info(""" A user will be created with the following information: - nickname: #{nickname} - email: #{email} @@ -128,10 +133,10 @@ def run(["new", nickname, email | rest]) do - admin: #{if(admin?, do: "true", else: "false")} """) - proceed? = assume_yes? or Common.shell_yes?("Continue?") + proceed? = assume_yes? or shell_yes?("Continue?") if proceed? do - Common.start_pleroma() + start_pleroma() params = %{ nickname: nickname, @@ -145,7 +150,7 @@ def run(["new", nickname, email | rest]) do changeset = User.register_changeset(%User{}, params, need_confirmation: false) {:ok, _user} = User.register(changeset) - Common.shell_info("User #{nickname} created") + shell_info("User #{nickname} created") if moderator? do run(["set", nickname, "--moderator"]) @@ -159,64 +164,64 @@ def run(["new", nickname, email | rest]) do run(["reset_password", nickname]) end else - Common.shell_info("User will not be created.") + shell_info("User will not be created.") end end def run(["rm", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do User.perform(:delete, user) - Common.shell_info("User #{nickname} deleted.") + shell_info("User #{nickname} deleted.") else _ -> - Common.shell_error("No local user #{nickname}") + shell_error("No local user #{nickname}") end end def run(["toggle_activated", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do {:ok, user} = User.deactivate(user, !user.info.deactivated) - Common.shell_info( + shell_info( "Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated" ) else _ -> - Common.shell_error("No user #{nickname}") + shell_error("No user #{nickname}") end end def run(["reset_password", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{local: true} = user <- User.get_cached_by_nickname(nickname), {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do - Common.shell_info("Generated password reset token for #{user.nickname}") + shell_info("Generated password reset token for #{user.nickname}") IO.puts( "URL: #{ - Pleroma.Web.Router.Helpers.util_url( + Pleroma.Web.Router.Helpers.reset_password_url( Pleroma.Web.Endpoint, - :show_password_reset, + :reset, token.token ) }" ) else _ -> - Common.shell_error("No local user #{nickname}") + shell_error("No local user #{nickname}") end end def run(["unsubscribe", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do - Common.shell_info("Deactivating #{user.nickname}") + shell_info("Deactivating #{user.nickname}") User.deactivate(user) {:ok, friends} = User.get_friends(user) @@ -224,7 +229,7 @@ def run(["unsubscribe", nickname]) do Enum.each(friends, fn friend -> user = User.get_cached_by_id(user.id) - Common.shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}") + shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}") User.unfollow(user, friend) end) @@ -233,16 +238,16 @@ def run(["unsubscribe", nickname]) do user = User.get_cached_by_id(user.id) if Enum.empty?(user.following) do - Common.shell_info("Successfully unsubscribed all followers from #{user.nickname}") + shell_info("Successfully unsubscribed all followers from #{user.nickname}") end else _ -> - Common.shell_error("No user #{nickname}") + shell_error("No user #{nickname}") end end def run(["set", nickname | rest]) do - Common.start_pleroma() + start_pleroma() {options, [], []} = OptionParser.parse( @@ -274,33 +279,33 @@ def run(["set", nickname | rest]) do end else _ -> - Common.shell_error("No local user #{nickname}") + shell_error("No local user #{nickname}") end end def run(["tag", nickname | tags]) do - Common.start_pleroma() + start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.tag(tags) - Common.shell_info("Tags of #{user.nickname}: #{inspect(tags)}") + shell_info("Tags of #{user.nickname}: #{inspect(tags)}") else _ -> - Common.shell_error("Could not change user tags for #{nickname}") + shell_error("Could not change user tags for #{nickname}") end end def run(["untag", nickname | tags]) do - Common.start_pleroma() + start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.untag(tags) - Common.shell_info("Tags of #{user.nickname}: #{inspect(tags)}") + shell_info("Tags of #{user.nickname}: #{inspect(tags)}") else _ -> - Common.shell_error("Could not change user tags for #{nickname}") + shell_error("Could not change user tags for #{nickname}") end end @@ -321,14 +326,12 @@ def run(["invite" | rest]) do end) |> Enum.into(%{}) - Common.start_pleroma() + start_pleroma() with {:ok, val} <- options[:expires_at], options = Map.put(options, :expires_at, val), {:ok, invite} <- UserInviteToken.create_invite(options) do - Common.shell_info( - "Generated user invite token " <> String.replace(invite.invite_type, "_", " ") - ) + shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " ")) url = Pleroma.Web.Router.Helpers.redirect_url( @@ -340,14 +343,14 @@ def run(["invite" | rest]) do IO.puts(url) else error -> - Common.shell_error("Could not create invite token: #{inspect(error)}") + shell_error("Could not create invite token: #{inspect(error)}") end end def run(["invites"]) do - Common.start_pleroma() + start_pleroma() - Common.shell_info("Invites list:") + shell_info("Invites list:") UserInviteToken.list_invites() |> Enum.each(fn invite -> @@ -361,7 +364,7 @@ def run(["invites"]) do " | Max use: #{max_use} Left use: #{max_use - invite.uses}" end - Common.shell_info( + shell_info( "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{ invite.used }#{expire_info}#{using_info}" @@ -370,40 +373,54 @@ def run(["invites"]) do end def run(["revoke_invite", token]) do - Common.start_pleroma() + start_pleroma() with {:ok, invite} <- UserInviteToken.find_by_token(token), {:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do - Common.shell_info("Invite for token #{token} was revoked.") + shell_info("Invite for token #{token} was revoked.") else - _ -> Common.shell_error("No invite found with token #{token}") + _ -> shell_error("No invite found with token #{token}") end end def run(["delete_activities", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do {:ok, _} = User.delete_user_activities(user) - Common.shell_info("User #{nickname} statuses deleted.") + shell_info("User #{nickname} statuses deleted.") else _ -> - Common.shell_error("No local user #{nickname}") + shell_error("No local user #{nickname}") end end def run(["toggle_confirmed", nickname]) do - Common.start_pleroma() + start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do {:ok, user} = User.toggle_confirmation(user) message = if user.info.confirmation_pending, do: "needs", else: "doesn't need" - Common.shell_info("#{nickname} #{message} confirmation.") + shell_info("#{nickname} #{message} confirmation.") else _ -> - Common.shell_error("No local user #{nickname}") + shell_error("No local user #{nickname}") + end + end + + def run(["sign_out", nickname]) do + start_pleroma() + + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do + OAuth.Token.delete_user_tokens(user) + OAuth.Authorization.delete_user_authorizations(user) + + shell_info("#{nickname} signed out from all apps.") + else + _ -> + shell_error("No local user #{nickname}") end end @@ -416,7 +433,7 @@ defp set_moderator(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Common.shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") + shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") user end @@ -429,7 +446,7 @@ defp set_admin(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Common.shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}") + shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}") user end @@ -442,7 +459,7 @@ defp set_locked(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Common.shell_info("Locked status of #{user.nickname}: #{user.info.locked}") + shell_info("Locked status of #{user.nickname}: #{user.info.locked}") user end end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index a8cbfa52a..cf880aa22 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -11,8 +11,17 @@ def start_link do def load_and_update_env do if Pleroma.Config.get([:instance, :dynamic_configuration]) and Ecto.Adapters.SQL.table_exists?(Pleroma.Repo, "config") do - Pleroma.Repo.all(Config) - |> Enum.each(&update_env(&1)) + for_restart = + Pleroma.Repo.all(Config) + |> Enum.map(&update_env(&1)) + + # We need to restart applications for loaded settings take effect + for_restart + |> Enum.reject(&(&1 in [:pleroma, :ok])) + |> Enum.each(fn app -> + Application.stop(app) + :ok = Application.start(app) + end) end end @@ -25,11 +34,15 @@ defp update_env(setting) do setting.key end + group = String.to_existing_atom(setting.group) + Application.put_env( - :pleroma, + group, String.to_existing_atom(key), Config.from_binary(setting.value) ) + + group rescue e -> require Logger diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 8502a0d0c..934620765 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -23,13 +23,8 @@ defp recipient(email, nil), do: email defp recipient(email, name), do: {name, email} defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name) - def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do - password_reset_url = - Router.Helpers.util_url( - Endpoint, - :show_password_reset, - password_reset_token - ) + def password_reset_email(user, token) when is_binary(token) do + password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) html_body = """

Reset your password at #{instance_name()}

diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index e25692006..a414afbbf 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -127,8 +127,7 @@ def dismiss(%{id: user_id} = _user, id) do end end - def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) - when type in ["Create", "Like", "Announce", "Follow"] do + def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do object = Object.normalize(activity) unless object && object.data["type"] == "Answer" do @@ -140,6 +139,13 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activit end end + def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) + when type in ["Like", "Announce", "Follow"] do + users = get_notified_from_activity(activity) + notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + {:ok, notifications} + end + def create_notifications(_), do: {:ok, []} # TODO move to sql, too. diff --git a/lib/pleroma/PasswordResetToken.ex b/lib/pleroma/password_reset_token.ex similarity index 93% rename from lib/pleroma/PasswordResetToken.ex rename to lib/pleroma/password_reset_token.ex index f31ea5bc5..4a833f6a5 100644 --- a/lib/pleroma/PasswordResetToken.ex +++ b/lib/pleroma/password_reset_token.ex @@ -37,6 +37,7 @@ def used_changeset(struct) do |> put_change(:used, true) end + @spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()} def reset_password(token, data) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), %User{} = user <- User.get_cached_by_id(token.user_id), diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex index eb6eff61c..8afabf463 100644 --- a/lib/pleroma/release_tasks.ex +++ b/lib/pleroma/release_tasks.ex @@ -17,6 +17,7 @@ def run(args) do end defp mix_task(task, args) do + Application.load(:pleroma) {:ok, modules} = :application.get_key(:pleroma, :modules) module = @@ -43,6 +44,8 @@ def rollback(args) do end def create do + Application.load(:pleroma) + case @repo.__adapter__.storage_up(@repo.config) do :ok -> IO.puts("The database for #{inspect(@repo)} has been created") diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex new file mode 100644 index 000000000..a4b71a1bb --- /dev/null +++ b/lib/pleroma/repo_streamer.ex @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.RepoStreamer do + alias Pleroma.Repo + import Ecto.Query + + def chunk_stream(query, chunk_size) do + Stream.unfold(0, fn + :halt -> + {[], :halt} + + last_id -> + query + |> order_by(asc: :id) + |> where([r], r.id > ^last_id) + |> limit(^chunk_size) + |> Repo.all() + |> case do + [] -> + {[], :halt} + + records -> + last_id = List.last(records).id + {records, last_id} + end + end) + |> Stream.take_while(fn + [] -> false + _ -> true + end) + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3a9ae8d73..f7191762f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -9,12 +9,14 @@ defmodule Pleroma.User do import Ecto.Query alias Comeonin.Pbkdf2 + alias Ecto.Multi alias Pleroma.Activity alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Registration alias Pleroma.Repo + alias Pleroma.RepoStreamer alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -193,27 +195,24 @@ def upgrade_changeset(struct, params \\ %{}) do end def password_update_changeset(struct, params) do - changeset = - struct - |> cast(params, [:password, :password_confirmation]) - |> validate_required([:password, :password_confirmation]) - |> validate_confirmation(:password) - - OAuth.Token.delete_user_tokens(struct) - OAuth.Authorization.delete_user_authorizations(struct) - - if changeset.valid? do - hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) - - changeset - |> put_change(:password_hash, hashed) - else - changeset - end + struct + |> cast(params, [:password, :password_confirmation]) + |> validate_required([:password, :password_confirmation]) + |> validate_confirmation(:password) + |> put_password_hash end - def reset_password(user, data) do - update_and_set_cache(password_update_changeset(user, data)) + def reset_password(%User{id: user_id} = user, data) do + multi = + Multi.new() + |> Multi.update(:user, password_update_changeset(user, data)) + |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id)) + |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user)) + + case Repo.transaction(multi) do + {:ok, %{user: user} = _} -> set_cache(user) + {:error, _, changeset, _} -> {:error, changeset} + end end def register_changeset(struct, params \\ %{}, opts \\ []) do @@ -249,12 +248,11 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do end if changeset.valid? do - hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]}) followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]}) changeset - |> put_change(:password_hash, hashed) + |> put_password_hash |> put_change(:ap_id, ap_id) |> unique_constraint(:ap_id) |> put_change(:following, [followers]) @@ -932,18 +930,24 @@ def delete(%User{} = user), @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do - {:ok, user} = User.deactivate(user) - # Remove all relationships {:ok, followers} = User.get_followers(user) - Enum.each(followers, fn follower -> User.unfollow(follower, user) end) + Enum.each(followers, fn follower -> + ActivityPub.unfollow(follower, user) + User.unfollow(follower, user) + end) {:ok, friends} = User.get_friends(user) - Enum.each(friends, fn followed -> User.unfollow(user, followed) end) + Enum.each(friends, fn followed -> + ActivityPub.unfollow(user, followed) + User.unfollow(user, followed) + end) delete_user_activities(user) + + {:ok, _user} = Repo.delete(user) end @spec perform(atom(), User.t()) :: {:ok, User.t()} @@ -1016,18 +1020,35 @@ def follow_import(%User{} = follower, followed_identifiers) when is_list(followe ]) def delete_user_activities(%User{ap_id: ap_id} = user) do - stream = - ap_id - |> Activity.query_by_actor() - |> Repo.stream() - - Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity) + ap_id + |> Activity.query_by_actor() + |> RepoStreamer.chunk_stream(50) + |> Stream.each(fn activities -> + Enum.each(activities, &delete_activity(&1)) + end) + |> Stream.run() {:ok, user} end defp delete_activity(%{data: %{"type" => "Create"}} = activity) do - Object.normalize(activity) |> ActivityPub.delete() + activity + |> Object.normalize() + |> ActivityPub.delete() + end + + defp delete_activity(%{data: %{"type" => "Like"}} = activity) do + user = get_cached_by_ap_id(activity.actor) + object = Object.normalize(activity) + + ActivityPub.unlike(user, object) + end + + defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do + user = get_cached_by_ap_id(activity.actor) + object = Object.normalize(activity) + + ActivityPub.unannounce(user, object) end defp delete_activity(_activity), do: "Doing nothing" @@ -1325,4 +1346,12 @@ def get_ap_ids_by_nicknames(nicknames) do end defdelegate search(query, opts \\ []), to: User.Search + + defp put_password_hash( + %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset + ) do + change(changeset, password_hash: Pbkdf2.hashpwsalt(password)) + end + + defp put_password_hash(changeset), do: changeset end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c0e3d1478..55315d66e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -189,6 +189,22 @@ def stream_out_participations(participations) do end) end + def stream_out_participations(%Object{data: %{"context" => context}}, user) do + with %Conversation{} = conversation <- Conversation.get_for_ap_id(context), + conversation = Repo.preload(conversation, :participations), + last_activity_id = + fetch_latest_activity_id_for_context(conversation.ap_id, %{ + "user" => user, + "blocking_user" => user + }) do + if last_activity_id do + stream_out_participations(conversation.participations) + end + end + end + + def stream_out_participations(_, _), do: :noop + def stream_out(activity) do public = "https://www.w3.org/ns/activitystreams#Public" @@ -401,7 +417,8 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru "to" => to, "deleted_activity_id" => activity && activity.id }, - {:ok, activity} <- insert(data, local), + {:ok, activity} <- insert(data, local, false), + stream_out_participations(object, user), _ <- decrease_replies_count_if_reply(object), # Changing note count prior to enqueuing federation task in order to avoid # race conditions on updating user.info diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex new file mode 100644 index 000000000..2da3eac2f --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do + alias Pleroma.User + + require Logger + + # has the user successfully posted before? + defp old_user?(%User{} = u) do + u.info.note_count > 0 || u.info.follower_count > 0 + end + + # does the post contain links? + defp contains_links?(%{"content" => content} = _object) do + content + |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl") + |> Floki.attribute("a", "href") + |> length() > 0 + end + + defp contains_links?(_), do: false + + def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do + with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor), + {:contains_links, true} <- {:contains_links, contains_links?(object)}, + {:old_user, true} <- {:old_user, old_user?(u)} do + {:ok, message} + else + {:contains_links, false} -> + {:ok, message} + + {:old_user, false} -> + {:reject, nil} + + {:error, _} -> + {:reject, nil} + + e -> + Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}") + {:reject, nil} + end + end + + # in all other cases, pass through + def filter(message), do: {:ok, message} +end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 10ff572a2..514266cee 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -151,16 +151,18 @@ def get_notified_from_object(object) do def create_context(context) do context = context || generate_id("contexts") - changeset = Object.context_mapping(context) - case Repo.insert(changeset) do - {:ok, object} -> + # Ecto has problems accessing the constraint inside the jsonb, + # so we explicitly check for the existed object before insert + object = Object.get_cached_by_ap_id(context) + + with true <- is_nil(object), + changeset <- Object.context_mapping(context), + {:ok, inserted_object} <- Repo.insert(changeset) do + inserted_object + else + _ -> object - - # This should be solved by an upsert, but it seems ecto - # has problems accessing the constraint inside the jsonb. - {:error, _} -> - Object.get_cached_by_ap_id(context) end end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 03dfdca82..953a22ea0 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -377,12 +377,12 @@ def config_update(conn, %{"configs" => configs}) do if Pleroma.Config.get([:instance, :dynamic_configuration]) do updated = Enum.map(configs, fn - %{"key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{key: key, value: value}) + %{"group" => group, "key" => key, "value" => value} -> + {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) config - %{"key" => key, "delete" => "true"} -> - {:ok, _} = Config.delete(key) + %{"group" => group, "key" => key, "delete" => "true"} -> + {:ok, _} = Config.delete(%{group: group, key: key}) nil end) |> Enum.reject(&is_nil(&1)) diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index b7072f050..8b9b658a9 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -12,26 +12,27 @@ defmodule Pleroma.Web.AdminAPI.Config do schema "config" do field(:key, :string) + field(:group, :string) field(:value, :binary) timestamps() end - @spec get_by_key(String.t()) :: Config.t() | nil - def get_by_key(key), do: Repo.get_by(Config, key: key) + @spec get_by_params(map()) :: Config.t() | nil + def get_by_params(params), do: Repo.get_by(Config, params) @spec changeset(Config.t(), map()) :: Changeset.t() def changeset(config, params \\ %{}) do config - |> cast(params, [:key, :value]) - |> validate_required([:key, :value]) - |> unique_constraint(:key) + |> cast(params, [:key, :group, :value]) + |> validate_required([:key, :group, :value]) + |> unique_constraint(:key, name: :config_group_key_index) end @spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def create(%{key: key, value: value}) do + def create(params) do %Config{} - |> changeset(%{key: key, value: transform(value)}) + |> changeset(Map.put(params, :value, transform(params[:value]))) |> Repo.insert() end @@ -43,20 +44,20 @@ def update(%Config{} = config, %{value: value}) do end @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def update_or_create(%{key: key} = params) do - with %Config{} = config <- Config.get_by_key(key) do + def update_or_create(params) do + with %Config{} = config <- Config.get_by_params(Map.take(params, [:group, :key])) do Config.update(config, params) else nil -> Config.create(params) end end - @spec delete(String.t()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def delete(key) do - with %Config{} = config <- Config.get_by_key(key) do + @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} + def delete(params) do + with %Config{} = config <- Config.get_by_params(params) do Repo.delete(config) else - nil -> {:error, "Config with key #{key} not found"} + nil -> {:error, "Config with params #{inspect(params)} not found"} end end @@ -77,10 +78,21 @@ defp do_convert(values) when is_list(values), do: for(val <- values, do: do_conv defp do_convert({k, v} = value) when is_tuple(value), do: %{k => do_convert(v)} - defp do_convert(value) when is_binary(value) or is_atom(value) or is_map(value), - do: value + defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))} + + defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value + + defp do_convert(value) when is_atom(value) do + string = to_string(value) + + if String.starts_with?(string, "Elixir."), + do: String.trim_leading(string, "Elixir."), + else: value + end @spec transform(any()) :: binary() + def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity)) + def transform(entity) when is_map(entity) do tuples = for {k, v} <- entity, @@ -101,11 +113,16 @@ def transform(entity), do: :erlang.term_to_binary(entity) defp do_transform(%Regex{} = value) when is_map(value), do: value + defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do + {do_transform(k), do_transform(values)} + end + + defp do_transform(%{"tuple" => values}) do + Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) + end + defp do_transform(value) when is_map(value) do - values = - for {key, val} <- value, - into: [], - do: {String.to_atom(key), do_transform(val)} + values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)} Enum.sort(values) end @@ -117,28 +134,27 @@ defp do_transform(value) when is_list(value) do defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity) defp do_transform(value) when is_binary(value) do - value = String.trim(value) - - case String.length(value) do - 0 -> - nil - - _ -> - cond do - String.starts_with?(value, "Pleroma") -> - String.to_existing_atom("Elixir." <> value) - - String.starts_with?(value, ":") -> - String.replace(value, ":", "") |> String.to_existing_atom() - - String.starts_with?(value, "i:") -> - String.replace(value, "i:", "") |> String.to_integer() - - true -> - value - end - end + String.trim(value) + |> do_transform_string() end defp do_transform(value), do: value + + defp do_transform_string(value) when byte_size(value) == 0, do: nil + + defp do_transform_string(value) do + cond do + String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") -> + String.to_existing_atom("Elixir." <> value) + + String.starts_with?(value, ":") -> + String.replace(value, ":", "") |> String.to_existing_atom() + + String.starts_with?(value, "i:") -> + String.replace(value, "i:", "") |> String.to_integer() + + true -> + value + end + end end diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index c8560033e..3ccc9ca46 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -10,6 +10,7 @@ def render("index.json", %{configs: configs}) do def render("show.json", %{config: config}) do %{ key: config.key, + group: config.group, value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value) } end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index 18973413e..d53e20d12 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -76,14 +76,16 @@ def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do def use_token(%Authorization{used: true}), do: {:error, "already used"} @spec delete_user_authorizations(User.t()) :: {integer(), any()} - def delete_user_authorizations(%User{id: user_id}) do - from( - a in Pleroma.Web.OAuth.Authorization, - where: a.user_id == ^user_id - ) + def delete_user_authorizations(%User{} = user) do + user + |> delete_by_user_query |> Repo.delete_all() end + def delete_by_user_query(%User{id: user_id}) do + from(a in __MODULE__, where: a.user_id == ^user_id) + end + @doc "gets auth for app by token" @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(%App{id: app_id} = _app, token) do diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 35a7c582e..3f8e3b074 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -64,26 +64,34 @@ defp do_authorize(%Plug.Conn{} = conn, params) do defp handle_existing_authorization( %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, - params + %{"redirect_uri" => @oob_token_redirect_uri} ) do - token = Repo.preload(token, :app) + render(conn, "oob_token_exists.html", %{token: token}) + end + + defp handle_existing_authorization( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{} = params + ) do + app = Repo.preload(token, :app).app redirect_uri = if is_binary(params["redirect_uri"]) do params["redirect_uri"] else - default_redirect_uri(token.app) + default_redirect_uri(app) end - redirect_uri = redirect_uri(conn, redirect_uri) - - if redirect_uri == @oob_token_redirect_uri do - render(conn, "oob_token_exists.html", %{token: token}) - else + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{access_token: token.token} url_params = UriHelper.append_param_if_present(url_params, :state, params["state"]) url = UriHelper.append_uri_params(redirect_uri, url_params) redirect(conn, external: url) + else + conn + |> put_flash(:error, "Unlisted redirect_uri.") + |> redirect(external: redirect_uri(conn, redirect_uri)) end end @@ -100,18 +108,28 @@ def create_authorization( end end + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ + "authorization" => %{"redirect_uri" => @oob_token_redirect_uri} + }) do + render(conn, "oob_authorization_created.html", %{auth: auth}) + end + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs }) do - redirect_uri = redirect_uri(conn, redirect_uri) + app = Repo.preload(auth, :app).app - if redirect_uri == @oob_token_redirect_uri do - render(conn, "oob_authorization_created.html", %{auth: auth}) - else + # An extra safety measure before we redirect (also done in `do_create_authorization/2`) + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{code: auth.token} url_params = UriHelper.append_param_if_present(url_params, :state, auth_attrs["state"]) url = UriHelper.append_uri_params(redirect_uri, url_params) redirect(conn, external: url) + else + conn + |> put_flash(:error, "Unlisted redirect_uri.") + |> redirect(external: redirect_uri(conn, redirect_uri)) end end @@ -324,7 +342,7 @@ def callback(%Plug.Conn{} = conn, params) do }) conn - |> put_session(:registration_id, registration.id) + |> put_session_registration_id(registration.id) |> registration_details(%{"authorization" => registration_params}) end else @@ -445,7 +463,7 @@ defp validate_scopes(app, params) do |> Scopes.validates(app.scopes) end - defp default_redirect_uri(%App{} = app) do + def default_redirect_uri(%App{} = app) do app.redirect_uris |> String.split() |> Enum.at(0) diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex index 82f1cce29..fb79630e4 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -34,13 +34,15 @@ defp normalize_attributes(html_node, prefix, key_name, value_name) do defp maybe_put_title(%{title: _} = meta, _), do: meta - defp maybe_put_title(meta, html) do + defp maybe_put_title(meta, html) when meta != %{} do case get_page_title(html) do "" -> meta title -> Map.put_new(meta, :title, title) end end + defp maybe_put_title(meta, _), do: meta + defp get_page_title(html) do Floki.find(html, "title") |> Floki.text() end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 837153ed4..c504116b6 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -133,8 +133,8 @@ defmodule Pleroma.Web.Router do scope "/api/pleroma", Pleroma.Web.TwitterAPI do pipe_through(:pleroma_api) - get("/password_reset/:token", UtilController, :show_password_reset) - post("/password_reset", UtilController, :password_reset) + get("/password_reset/:token", PasswordController, :reset, as: :reset_password) + post("/password_reset", PasswordController, :do_reset, as: :reset_password) get("/emoji", UtilController, :emoji) get("/captcha", UtilController, :captcha) get("/healthcheck", UtilController, :healthcheck) diff --git a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex rename to lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex similarity index 82% rename from lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex rename to lib/pleroma/web/templates/twitter_api/password/reset.html.eex index a3facf017..7d3ef6b0d 100644 --- a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex @@ -1,5 +1,5 @@

Password Reset for <%= @user.nickname %>

-<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %> +<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
<%= label f, :password, "Password" %> <%= password_input f, :password %> diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex rename to lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex rename to lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex new file mode 100644 index 000000000..1941e6143 --- /dev/null +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.PasswordController do + @moduledoc """ + The module containts functions for reset password. + """ + + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.PasswordResetToken + alias Pleroma.Repo + alias Pleroma.User + + def reset(conn, %{"token" => token}) do + with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), + %User{} = user <- User.get_cached_by_id(token.user_id) do + render(conn, "reset.html", %{ + token: token, + user: user + }) + else + _e -> render(conn, "invalid_token.html") + end + end + + def do_reset(conn, %{"data" => data}) do + with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do + render(conn, "reset_success.html") + else + _e -> render(conn, "reset_failed.html") + end + end +end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 489170d80..b1863528f 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Activity alias Pleroma.Emoji alias Pleroma.Notification - alias Pleroma.PasswordResetToken - alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web.OStatus alias Pleroma.Web.WebFinger - def show_password_reset(conn, %{"token" => token}) do - with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- User.get_cached_by_id(token.user_id) do - render(conn, "password_reset.html", %{ - token: token, - user: user - }) - else - _e -> render(conn, "invalid_token.html") - end - end - - def password_reset(conn, %{"data" => data}) do - with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do - render(conn, "password_reset_success.html") - else - _e -> render(conn, "password_reset_failed.html") - end - end - def help_test(conn, _params) do json(conn, "ok") end diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex new file mode 100644 index 000000000..b166b925d --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/password_view.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.PasswordView do + use Pleroma.Web, :view + import Phoenix.HTML.Form +end diff --git a/mix.exs b/mix.exs index 7f8e36cbb..5484c43ef 100644 --- a/mix.exs +++ b/mix.exs @@ -37,14 +37,14 @@ def project do pleroma: [ include_executables_for: [:unix], applications: [ex_syslogger: :load, syslog: :load], - steps: [:assemble, ©_pleroma_ctl/1] + steps: [:assemble, ©_files/1] ] ] ] end - def copy_pleroma_ctl(%{path: target_path} = release) do - File.cp!("./rel/pleroma_ctl", Path.join([target_path, "bin", "pleroma_ctl"])) + def copy_files(%{path: target_path} = release) do + File.cp_r!("./rel/files", target_path) release end @@ -108,7 +108,7 @@ defp deps do {:ex_aws, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"}, {:earmark, "~> 1.3"}, - {:bbcode, "~> 0.1"}, + {:bbcode, "~> 0.1.1"}, {:ex_machina, "~> 2.3", only: :test}, {:credo, "~> 0.9.3", only: [:dev, :test]}, {:mock, "~> 0.3.3", only: :test}, @@ -209,10 +209,11 @@ defp version(version) do branch_name = with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]), + branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name, true <- branch_name != "master" do branch_name = String.trim(branch_name) - |> String.replace(~r/\W+/, "-") + |> String.replace(~r/[^0-9a-z\-\.]+/i, "-") "-" <> branch_name end diff --git a/mix.lock b/mix.lock index 6528db98d..cae8d7d84 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/repo/migrations/20190622151019_add_group_key_to_config.exs b/priv/repo/migrations/20190622151019_add_group_key_to_config.exs new file mode 100644 index 000000000..d7a3785d0 --- /dev/null +++ b/priv/repo/migrations/20190622151019_add_group_key_to_config.exs @@ -0,0 +1,12 @@ +defmodule Pleroma.Repo.Migrations.AddGroupKeyToConfig do + use Ecto.Migration + + def change do + alter table("config") do + add(:group, :string) + end + + drop(unique_index("config", :key)) + create(unique_index("config", [:group, :key])) + end +end diff --git a/priv/static/adminfe/static/css/app.cea15678.css b/priv/static/adminfe/app.34fc670f.css similarity index 100% rename from priv/static/adminfe/static/css/app.cea15678.css rename to priv/static/adminfe/app.34fc670f.css diff --git a/priv/static/adminfe/static/css/chunk-18e1.6aaab273.css b/priv/static/adminfe/chunk-18e1.6aaab273.css similarity index 100% rename from priv/static/adminfe/static/css/chunk-18e1.6aaab273.css rename to priv/static/adminfe/chunk-18e1.6aaab273.css diff --git a/priv/static/adminfe/chunk-56c9.c27dac5e.css b/priv/static/adminfe/chunk-56c9.c27dac5e.css new file mode 100644 index 000000000..2b4283ec5 Binary files /dev/null and b/priv/static/adminfe/chunk-56c9.c27dac5e.css differ diff --git a/priv/static/adminfe/chunk-5eaf.1a04e979.css b/priv/static/adminfe/chunk-5eaf.1a04e979.css new file mode 100644 index 000000000..a09287f58 Binary files /dev/null and b/priv/static/adminfe/chunk-5eaf.1a04e979.css differ diff --git a/priv/static/adminfe/static/css/chunk-8b70.9ba0945c.css b/priv/static/adminfe/chunk-8b70.9ba0945c.css similarity index 100% rename from priv/static/adminfe/static/css/chunk-8b70.9ba0945c.css rename to priv/static/adminfe/chunk-8b70.9ba0945c.css diff --git a/priv/static/adminfe/chunk-elementUI.f74c256b.css b/priv/static/adminfe/chunk-elementUI.f74c256b.css new file mode 100644 index 000000000..c8d56344e Binary files /dev/null and b/priv/static/adminfe/chunk-elementUI.f74c256b.css differ diff --git a/priv/static/adminfe/static/css/chunk-f018.0d22684d.css b/priv/static/adminfe/chunk-f018.0d22684d.css similarity index 100% rename from priv/static/adminfe/static/css/chunk-f018.0d22684d.css rename to priv/static/adminfe/chunk-f018.0d22684d.css diff --git a/priv/static/adminfe/static/css/chunk-libs.bd17d456.css b/priv/static/adminfe/chunk-libs.4e8c4664.css similarity index 100% rename from priv/static/adminfe/static/css/chunk-libs.bd17d456.css rename to priv/static/adminfe/chunk-libs.4e8c4664.css diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html index 44a58a44e..2ef6362ba 100644 --- a/priv/static/adminfe/index.html +++ b/priv/static/adminfe/index.html @@ -1 +1 @@ -Admin FE
\ No newline at end of file +Admin FE
\ No newline at end of file diff --git a/priv/static/adminfe/static/css/chunk-50cf.1db1ed5b.css b/priv/static/adminfe/static/css/chunk-50cf.1db1ed5b.css deleted file mode 100644 index 2a2fbd8e4..000000000 Binary files a/priv/static/adminfe/static/css/chunk-50cf.1db1ed5b.css and /dev/null differ diff --git a/priv/static/adminfe/static/css/chunk-elementUI.4296cedf.css b/priv/static/adminfe/static/css/chunk-elementUI.4296cedf.css deleted file mode 100644 index fbd926db1..000000000 Binary files a/priv/static/adminfe/static/css/chunk-elementUI.4296cedf.css and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.25699e3d.js b/priv/static/adminfe/static/js/app.25699e3d.js deleted file mode 100644 index 54694bf5e..000000000 Binary files a/priv/static/adminfe/static/js/app.25699e3d.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.4137ad8f.js b/priv/static/adminfe/static/js/app.4137ad8f.js new file mode 100644 index 000000000..bb4b6ec49 Binary files /dev/null and b/priv/static/adminfe/static/js/app.4137ad8f.js differ diff --git a/priv/static/adminfe/static/js/chunk-02a0.db6ec114.js b/priv/static/adminfe/static/js/chunk-02a0.db6ec114.js new file mode 100644 index 000000000..6f8dd4d13 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-02a0.db6ec114.js differ diff --git a/priv/static/adminfe/static/js/chunk-0620.c765c190.js b/priv/static/adminfe/static/js/chunk-0620.c765c190.js new file mode 100644 index 000000000..aa8ddedce Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-0620.c765c190.js differ diff --git a/priv/static/adminfe/static/js/chunk-50cf.b9b1df43.js b/priv/static/adminfe/static/js/chunk-50cf.b9b1df43.js deleted file mode 100644 index 1b5614639..000000000 Binary files a/priv/static/adminfe/static/js/chunk-50cf.b9b1df43.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-56c9.28e35fc3.js b/priv/static/adminfe/static/js/chunk-56c9.28e35fc3.js new file mode 100644 index 000000000..6f92e0e72 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-56c9.28e35fc3.js differ diff --git a/priv/static/adminfe/static/js/chunk-5eaf.5b76e416.js b/priv/static/adminfe/static/js/chunk-5eaf.5b76e416.js new file mode 100644 index 000000000..56f1b6891 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-5eaf.5b76e416.js differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.1fa5434b.js b/priv/static/adminfe/static/js/chunk-elementUI.1fa5434b.js new file mode 100644 index 000000000..8f6f193b7 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-elementUI.1fa5434b.js differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.d388c21d.js b/priv/static/adminfe/static/js/chunk-elementUI.d388c21d.js deleted file mode 100644 index 1f40d7e84..000000000 Binary files a/priv/static/adminfe/static/js/chunk-elementUI.d388c21d.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-libs.48e79a9e.js b/priv/static/adminfe/static/js/chunk-libs.d5609760.js similarity index 79% rename from priv/static/adminfe/static/js/chunk-libs.48e79a9e.js rename to priv/static/adminfe/static/js/chunk-libs.d5609760.js index db0b5dc97..5fa09cf69 100644 Binary files a/priv/static/adminfe/static/js/chunk-libs.48e79a9e.js and b/priv/static/adminfe/static/js/chunk-libs.d5609760.js differ diff --git a/priv/static/adminfe/static/js/runtime.7144b2cf.js b/priv/static/adminfe/static/js/runtime.7144b2cf.js deleted file mode 100644 index 0a58ac351..000000000 Binary files a/priv/static/adminfe/static/js/runtime.7144b2cf.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/runtime.d8d12c12.js b/priv/static/adminfe/static/js/runtime.d8d12c12.js new file mode 100644 index 000000000..6d8ac5af6 Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.d8d12c12.js differ diff --git a/lib/mix/tasks/pleroma/robots_txt.eex b/priv/templates/robots_txt.eex similarity index 100% rename from lib/mix/tasks/pleroma/robots_txt.eex rename to priv/templates/robots_txt.eex diff --git a/lib/mix/tasks/pleroma/sample_config.eex b/priv/templates/sample_config.eex similarity index 88% rename from lib/mix/tasks/pleroma/sample_config.eex rename to priv/templates/sample_config.eex index 73d9217be..2d4a49328 100644 --- a/lib/mix/tasks/pleroma/sample_config.eex +++ b/priv/templates/sample_config.eex @@ -3,7 +3,11 @@ # NOTE: This file should not be committed to a repo or otherwise made public # without removing sensitive information. -use Mix.Config +<%= if Code.ensure_loaded?(Config) or not Code.ensure_loaded?(Mix.Config) do + "import Config" +else + "use Mix.Config" +end %> config :pleroma, Pleroma.Web.Endpoint, url: [host: "<%= domain %>", scheme: "https", port: <%= port %>], @@ -16,7 +20,6 @@ config :pleroma, :instance, notify_email: "<%= notify_email %>", limit: 5000, registrations_open: true, - dedupe_media: false, dynamic_configuration: <%= db_configurable? %> config :pleroma, :media_proxy, @@ -38,6 +41,10 @@ config :web_push_encryption, :vapid_details, public_key: "<%= web_push_public_key %>", private_key: "<%= web_push_private_key %>" +config :pleroma, :database, rum_enabled: <%= rum_enabled %> +config :pleroma, :instance, static_dir: "<%= static_dir %>" +config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>" + # Enable Strict-Transport-Security once SSL is working: # config :pleroma, :http_security, # sts: true diff --git a/lib/mix/tasks/pleroma/sample_psql.eex b/priv/templates/sample_psql.eex similarity index 80% rename from lib/mix/tasks/pleroma/sample_psql.eex rename to priv/templates/sample_psql.eex index f0ac05e57..627839a68 100644 --- a/lib/mix/tasks/pleroma/sample_psql.eex +++ b/priv/templates/sample_psql.eex @@ -5,3 +5,8 @@ CREATE DATABASE <%= dbname %> OWNER <%= dbuser %>; CREATE EXTENSION IF NOT EXISTS citext; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +<%= if rum_enabled do + "CREATE EXTENSION IF NOT EXISTS rum;" +else +"" +end %> diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl new file mode 100755 index 000000000..b0e1874a9 --- /dev/null +++ b/rel/files/bin/pleroma_ctl @@ -0,0 +1,118 @@ +#!/bin/sh +# XXX: This should be removed when elixir's releases get custom command support + +detect_flavour() { + arch="$(arch)" + if [ "$arch" = "x86_64" ]; then + arch="amd64" + elif [ "$arch" = "armv7l" ]; then + arch="arm" + elif [ "$arch" = "aarch64" ]; then + arch="arm64" + else + echo "Unsupported arch: $arch" >&2 + exit 1 + fi + + if getconf GNU_LIBC_VERSION >/dev/null; then + libc_postfix="" + elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then + libc_postfix="-musl" + elif [ "$(find /lib/libc.musl* | wc -l)" ]; then + libc_postfix="-musl" + else + echo "Unsupported libc" >&2 + exit 1 + fi + + echo "$arch$libc_postfix" +} + +detect_branch() { + version="$(cut -d' ' -f2 <"$RELEASE_ROOT"/releases/start_erl.data)" + branch="$(echo "$version" | cut -d'-' -f 4)" + if [ "$branch" = "develop" ]; then + echo "develop" + elif [ "$branch" = "" ]; then + echo "master" + else + echo "Releases are built only for master and develop branches" >&2 + exit 1 + fi +} +update() { + set -e + RELEASE_ROOT=$(dirname "$SCRIPTPATH") + uri="${PLEROMA_CTL_URI:-https://git.pleroma.social}" + project_id="${PLEROMA_CTL_PROJECT_ID:-2}" + project_branch="$(detect_branch)" + flavour="${PLEROMA_CTL_FLAVOUR:-$(detect_flavour)}" + echo "Detected flavour: $flavour" + tmp="${PLEROMA_CTL_TMP_DIR:-/tmp}" + artifact="$tmp/pleroma.zip" + full_uri="${uri}/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=${flavour}" + echo "Downloading the artifact from ${full_uri} to ${artifact}" + curl "$full_uri" -o "${artifact}" + echo "Unpacking ${artifact} to ${tmp}" + unzip -q "$artifact" -d "$tmp" + echo "Copying files over to $RELEASE_ROOT" + if [ "$1" != "--no-rm" ]; then + rm -r "${RELEASE_ROOT:-?}"/* + fi + cp -rf "$tmp/release"/* "$RELEASE_ROOT" + echo "Removing temporary files" + rm -r "$tmp/release" + rm "$artifact" + echo "Done! Please refer to the changelog/release notes for changes and update instructions" + set +e +} + +if [ -z "$1" ] || [ "$1" = "help" ]; then + # TODO: Just list the commands on `pleroma_ctl help` and output help for the individual command on `pleroma_ctl help $COMMAND` + echo "Usage: $(basename "$0") COMMAND [ARGS] + + The known commands are: + + create + Create database schema (needs to be executed only once) + + migrate + Execute database migrations (needs to be done after updates) + + rollback [VERSION] + Rollback database migrations (needs to be done before downgrading) + + update [OPTIONS] + Update the instance using the latest CI artifact for the current branch. + + The only supported option is --no-rm, when set the script won't delete the whole directory, but + just force copy over files from the new release. This wastes more space, but may be useful if + some files are stored inside of the release directories (although you really shouldn't store them + there), or if you want to be able to quickly revert a broken update. + + The script will try to detect your architecture and ABI and set a flavour automatically, + but if it is wrong, you can overwrite it by setting PLEROMA_CTL_FLAVOUR to the desired flavour. + + By default the artifact will be downloaded from https://git.pleroma.social for pleroma/pleroma (project id: 2) + to /tmp/, you can overwrite these settings by setting PLEROMA_CTL_URI, PLEROMA_CTL_PROJECT_ID and PLEROMA_CTL_TMP_DIR + respectively. + + + and any mix tasks under Pleroma namespace, for example \`mix pleroma.user COMMAND\` is + equivalent to \`$(basename "$0") user COMMAND\` + + By default pleroma_ctl will try calling into a running instance to execute non migration-related commands, + if for some reason this is undesired, set PLEROMA_CTL_RPC_DISABLED environment variable +" +else + SCRIPT=$(readlink -f "$0") + SCRIPTPATH=$(dirname "$SCRIPT") + + if [ "$1" = "update" ]; then + update "$2" + elif [ "$1" = "migrate" ] || [ "$1" = "rollback" ] || [ "$1" = "create" ] || [ "$1 $2" = "instance gen" ] || [ -n "$PLEROMA_CTL_RPC_DISABLED" ]; then + "$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run("'"$*"'")' + else + "$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$*"'")' + fi +fi diff --git a/rel/pleroma_ctl b/rel/pleroma_ctl deleted file mode 100755 index ac7339762..000000000 --- a/rel/pleroma_ctl +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -# XXX: This should be removed when elixir's releases get custom command support -if [ -z "$1" ] || [ "$1" = "help" ]; then - echo "Usage: $(basename "$0") COMMAND [ARGS] - - The known commands are: - - create Create database schema (needs to be executed only once) - migrate Execute database migrations (needs to be done after updates) - rollback [VERSION] Rollback database migrations (needs to be done before downgrading) - - and any mix tasks under Pleroma namespace, for example \`mix pleroma.user COMMAND\` is - equivalent to \`$(basename "$0") user COMMAND\` - - By default pleroma_ctl will try calling into a running instance to execute non migration-related commands, - if for some reason this is undesired, set PLEROMA_CTL_RPC_DISABLED environment variable -" -else - SCRIPT=$(readlink -f "$0") - SCRIPTPATH=$(dirname "$SCRIPT") - if [ "$1" = "migrate" ] || [ "$1" = "rollback" ] || [ "$1" = "create" ] || [ -n "$PLEROMA_CTL_RPC_DISABLED" ]; then - "$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run("'"$*"'")' - else - "$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$*"'")' - fi -fi diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs index 9b8a8dd45..c0e433263 100644 --- a/test/config/transfer_task_test.exs +++ b/test/config/transfer_task_test.exs @@ -13,19 +13,37 @@ defmodule Pleroma.Config.TransferTaskTest do test "transfer config values from db to env" do refute Application.get_env(:pleroma, :test_key) - Pleroma.Web.AdminAPI.Config.create(%{key: "test_key", value: [live: 2, com: 3]}) + refute Application.get_env(:idna, :test_key) + + Pleroma.Web.AdminAPI.Config.create(%{ + group: "pleroma", + key: "test_key", + value: [live: 2, com: 3] + }) + + Pleroma.Web.AdminAPI.Config.create(%{ + group: "idna", + key: "test_key", + value: [live: 15, com: 35] + }) Pleroma.Config.TransferTask.start_link() assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3] + assert Application.get_env(:idna, :test_key) == [live: 15, com: 35] on_exit(fn -> Application.delete_env(:pleroma, :test_key) + Application.delete_env(:idna, :test_key) end) end test "non existing atom" do - Pleroma.Web.AdminAPI.Config.create(%{key: "undefined_atom_key", value: [live: 2, com: 3]}) + Pleroma.Web.AdminAPI.Config.create(%{ + group: "pleroma", + key: "undefined_atom_key", + value: [live: 2, com: 3] + }) assert ExUnit.CaptureLog.capture_log(fn -> Pleroma.Config.TransferTask.start_link() diff --git a/test/fixtures/rich_media/non_ogp_embed.html b/test/fixtures/rich_media/non_ogp_embed.html new file mode 100644 index 000000000..62a1d677a --- /dev/null +++ b/test/fixtures/rich_media/non_ogp_embed.html @@ -0,0 +1,1479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BlueAngelLove's Homepage on MyFreeCams.com + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + + +
Your Time:
MyFreeCams Time:
+
+ +
+
+ +
+
+
+
+
+ +
+ +
+ +
+
+ BlueAngelLove +
+ +
+
+ Status: +
+
+ +  - Model - + +
+
+ + + +
+ + Profile Headline: + + + + Enjoy and Love + +
+ + + + + + + + +
+ + Last Broadcast: + + + +
+ + + + + +
+ + Last Updated: + + + +
+ + + +
+
+
+
+ +
+ +
+
+
+
+ + +
+ My Most Recent Pictures +
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+ About Me +
+ + + +
+ + Username: + + + + BlueAngelLove +
+ + + + + + + + +
+ + Gender: + + + + Female +
+ + + + + +
+ + Body Type: + + + + Athletic +
+ + + + + +
+ + Ethnicity: + + + + Other +
+ + + + + +
+ + Hair: + + + + Brown +
+ + + + + +
+ + Eyes: + + + + Blue +
+ + + + + +
+ + Weight: + + + + 45 kilos +
+ + + + + +
+ + Height: + + + + 165 centimeters +
+ + + + + +
+ + Age: + + + + 34 +
+ + + + + +
+ + City: + + + + Mountains +
+ + + + + + + + + + + +
+ + Sexual Preference: + + + + Bisexual +
+ + + + + +
+ + Smoke: + + + + Non Smoker +
+ + + + + +
+ + Drink: + + + + Non Drinker +
+ + + + + +
+ + Drugs: + + + + Never +
+ + + + + + + + +
+ + Occupation/Major: + + + + Guide +
+ + + + + + + + +
+ + Favorite Food: + + + + Chocolate +
+ + + + + +
+ + Pets: + + + + I dont like pets +
+ + + + + +
+ + Automobile: + + + + Ford +
+ + + + + +
+ + About Me: + + + + DMCA.com Protection Statuswebsite counter +
+
+
BlueAngelLove
+
+ CONTACT ME + +
+
Angel
+
+
+ I Want To Be Seduced +
+
+
+ I Love Flirt +
+
+
+ I Want Be Part Of Your life +
+
+
+ I Love Dancing +
+
+
+ I Love Erotic Chats +
+
+
+ I am Funny +
+
+
+ I Enjoy C2C +
+
+
+ I Love Sex and Feel u +
+
+
+
+
+
June Month Contestst-Top 3 tippers Get A gift mailed,videos,pictures and will my right hand full month and room helpers as well (be my men for a month or who knows...maybe forever) *** +Love Ya Angels*** We are currently ranked #2200 overall
+

Get Listed on My Wall of Fame

+
+
+ +

ElmosEgo 6570 Tks

+

Rw2lite 4800 Tks

+

Toastboi 2093 Tks

+

Acoolahole 1850 Tks

+

Gonodog 1299 Tks

+

Pumpy_G 800 Tks

+

Fowser 690 Tks

+

Aquanautic 600 Tks

+

Daveonthelake 535 Tks

+

Wildpervert2 500 Tks

+

Cloud10101 350 Tks

+

Branson102 337 Tks

+

TheCopperhead 329 Tks

+

Mouche99 250 Tks

+

The88drummer 233 Tks

+

Stringtrees86 199 Tks

+

Blazegordon 183 Tks

+

Waiting_4 183 Tks

+

Sam_mie 170 Tks

+

UtterTripe 150 Tks

+

Darth_penguin 150 Tks

+

Playfullpurv 120 Tks

+

Jordnsprings 103 Tks

+

Travelinlover 100 Tks

+

Da884 100 Tks

+ +
+ cory1 +
+
May Contest winners - Each month Top 3 tippers Get A gift mailed, videos and pictures - Love Ya Angels
+

Get Listed on My Wall of Fame

+
+
+

Rw2lite

+

ElmosEgo

+

TJuonesWoah

+ +
+ cory1 +
+
Menu Per Day
+

LOVE YA ANGELS

+
+
+

Monday - Outfits Strip

+

Tusday - Raffle

+

Wensday - Gamblers Night

+

Thusday - Orgasmic Vibra or Dildos

+

Friday - Wheel/Treat or Trick

+

Saturday - Phrase

+

Sunday - Keno and Boyfriend choice

+ + + +
+
You have to tip to get listed above so do your best to get on my exclusive Top Tippers List
+
+
+ Whats-App-Image-2019-01-09-at-10-35-17 + 45280406-1564895203655742-4887638015087738880-n + best + Whats-App-Image-2019-01-09-at-10-35-16 + + + + + + + + + + + + + + + + +
+
+
+
+ +Whats-App-Image-2019-05-12-at-05-55-35-1lovense-level
+
+
+
+
+camgirl xxx amateur sex sexy
+
+
+
+
+39741863-284302529029606-7659956026455621632-n +
+Let's Fun Laugh Live
I am Jullia from Transylvania and is a pleasure to have u around Enjoy me and my room

BlueAngelLove
I am good, but not an angel. I do sin, but I am not the devil. I am just a girl in a big world trying to find someone to love and be loved

+20171114-113848 +
~~~Live~Laugh~Love~~~Flag Counter +
+
+ + + + + +
+ + Tags: + + + + natural, blue eyes, toys, funny, oil, shower, fetish, costume, sex, natural, masturbation, finger, dp, anal, girl next door, romantic, naughty, pervert, open mind, play roles, horny, playful, smiley, lover, sweet, sexy, beautiful, hot, shaved, friendly, pussy, skype +
+ + + +
+ +
+ +
+ Friends +
+ +
+ + Average Rating: + + + + + +
+ +
+ + Rate BlueAngelLove: + + + + +
+ +
+ + Admirers: +
+
+ + (admire) +
+ +
+ + + +
+ + Favorite Models: + + + BlueAngelLove + +
+
+
+
+ +
+
+
+
+
+ +
+ Password Protected Galleries +
+ + + +
+
+ +
+ +
+
+
+
+ +
+ Photo Galleries +
+ + + +
+
+ +
+ +
+
+
+
+ +
+ My Schedule +
+
+ + Sunday + + + + I'm + Always + online from + 3:30 am + until + 7:00 am + +
+
+ + Monday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+
+ + Tuesday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+
+ + Wednesday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+
+ + Thursday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+
+ + Friday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+
+ + Saturday + + + + I'm + Always + online from + 3:30 pm + until + 7:00 am + +
+ +
+
+ +
+
+
+
+
+ +
+ Interests & Hobbies +
+ + + +
+ + Meaning of Life: + + + + Meaning of Life .To Love and Be Loved and keep what i have and who i have in my life right now +
+ + + + + +
+ + Five Things I Can't Live Without: + + + + -family +-phone +-sex +-love +-money +
+ + + + + +
+ + Favorite Books: + + + + My fav. book was Count of Monte Cristo +
+ + + + + +
+ + What I Like To Do For Fun: + + + + In my free time I dance, play games , go out and travel +
+ + + + + +
+ + Favorite Songs: + + + +
+
+ + + + + +
+ + Favorite Movies: + + + + +
+ + + + + + + + + + + +
+ + Hobbies: + + + + My Amazon Wishlistuk +
+ + + + + +
+ + Talents: + + + + i love to Dance , Travel and Cook +
+ + + + + +
+ + Perfect Mate: + + + + Perfect mate is Magic Mike +
+ + + + + +
+ + Perfect Date: + + + + Perfect Date .You and Me , romatic dinner and wild sex +
+ + + + + +
+ + Turn Ons/Offs: + + + + I hate Liers and Rude Peoples +
+ + + + + +
+ + Best Reason to Get to Know Me: + + + + My dear men, please dont put a label on medont make me a category before you get to know me! +
+ + + +
+
+ +
+ +
+ + + + +
+
+

Send MFC Mail to BlueAngelLove

+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index b3798bf03..b8d6aff89 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -20,7 +20,7 @@ test "ip/1" do end test "it restricts by opts" do - scale = 100 + scale = 1000 limit = 5 Pleroma.Config.put([:rate_limit, @limiter_name], {scale, limit}) @@ -64,7 +64,7 @@ test "it restricts by opts" do test "optional limits for authenticated users" do Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo) - scale = 100 + scale = 1000 limit = 5 Pleroma.Config.put([:rate_limit, @limiter_name], [{1, 10}, {scale, limit}]) diff --git a/test/support/factory.ex b/test/support/factory.ex index 5be34660e..c2812e8f7 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -314,6 +314,7 @@ def registration_factory do def config_factory do %Pleroma.Web.AdminAPI.Config{ key: sequence(:key, &"some_key_#{&1}"), + group: "pleroma", value: sequence( :value, diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs index 7d3b1860c..9c9a31bf4 100644 --- a/test/tasks/config_test.exs +++ b/test/tasks/config_test.exs @@ -5,7 +5,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do setup_all do Mix.shell(Mix.Shell.Process) - temp_file = "config/temp.migrated.secret.exs" + temp_file = "config/temp.exported_from_db.secret.exs" dynamic = Pleroma.Config.get([:instance, :dynamic_configuration]) @@ -30,17 +30,26 @@ test "settings are migrated to db" do Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) - first_db = Config.get_by_key("first_setting") - second_db = Config.get_by_key("second_setting") - refute Config.get_by_key("Pleroma.Repo") + first_db = Config.get_by_params(%{group: "pleroma", key: "first_setting"}) + second_db = Config.get_by_params(%{group: "pleroma", key: "second_setting"}) + refute Config.get_by_params(%{group: "pleroma", key: "Pleroma.Repo"}) assert Config.from_binary(first_db.value) == [key: "value", key2: [Pleroma.Repo]] assert Config.from_binary(second_db.value) == [key: "value2", key2: [Pleroma.Activity]] end test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do - Config.create(%{key: "setting_first", value: [key: "value", key2: [Pleroma.Activity]]}) - Config.create(%{key: "setting_second", value: [key: "valu2", key2: [Pleroma.Repo]]}) + Config.create(%{ + group: "pleroma", + key: "setting_first", + value: [key: "value", key2: [Pleroma.Activity]] + }) + + Config.create(%{ + group: "pleroma", + key: "setting_second", + value: [key: "valu2", key2: [Pleroma.Repo]] + }) Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "temp"]) diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 6fd7c7113..3d4b08fba 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -89,8 +89,7 @@ test "user is deleted" do assert_received {:mix_shell, :info, [message]} assert message =~ " deleted" - user = User.get_cached_by_nickname(user.nickname) - assert user.info.deactivated + refute User.get_by_nickname(user.nickname) end test "no user to delete" do diff --git a/test/user_test.exs b/test/user_test.exs index a8176025c..198a97fae 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -920,42 +920,44 @@ test ".delete_user_activities deletes all create activities" do {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) - Ecto.Adapters.SQL.Sandbox.unboxed_run(Repo, fn -> - {:ok, _} = User.delete_user_activities(user) - # TODO: Remove favorites, repeats, delete activities. - refute Activity.get_by_id(activity.id) - end) + {:ok, _} = User.delete_user_activities(user) + + # TODO: Remove favorites, repeats, delete activities. + refute Activity.get_by_id(activity.id) end - test ".delete deactivates a user, all follow relationships and all create activities" do + test ".delete deactivates a user, all follow relationships and all activities" do user = insert(:user) - followed = insert(:user) follower = insert(:user) - {:ok, user} = User.follow(user, followed) {:ok, follower} = User.follow(follower, user) {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) {:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"}) - {:ok, _, _} = CommonAPI.favorite(activity_two.id, user) - {:ok, _, _} = CommonAPI.favorite(activity.id, follower) - {:ok, _, _} = CommonAPI.repeat(activity.id, follower) + {:ok, like, _} = CommonAPI.favorite(activity_two.id, user) + {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) + {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user) {:ok, _} = User.delete(user) - followed = User.get_cached_by_id(followed.id) follower = User.get_cached_by_id(follower.id) - user = User.get_cached_by_id(user.id) - assert user.info.deactivated + refute User.following?(follower, user) + refute User.get_by_id(user.id) - refute User.following?(user, followed) - refute User.following?(followed, follower) + user_activities = + user.ap_id + |> Activity.query_by_actor() + |> Repo.all() + |> Enum.map(fn act -> act.data["type"] end) - # TODO: Remove favorites, repeats, delete activities. + assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end) refute Activity.get_by_id(activity.id) + refute Activity.get_by_id(like.id) + refute Activity.get_by_id(like_two.id) + refute Activity.get_by_id(repeat.id) end test "get_public_key_for_ap_id fetches a user that's not in the db" do diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs new file mode 100644 index 000000000..03dc299ec --- /dev/null +++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs @@ -0,0 +1,145 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do + use Pleroma.DataCase + import Pleroma.Factory + import ExUnit.CaptureLog + + alias Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy + + @linkless_message %{ + "type" => "Create", + "object" => %{ + "content" => "hi world!" + } + } + + @linkful_message %{ + "type" => "Create", + "object" => %{ + "content" => "hi world!" + } + } + + @response_message %{ + "type" => "Create", + "object" => %{ + "name" => "yes", + "type" => "Answer" + } + } + + describe "with new user" do + test "it allows posts without links" do + user = insert(:user) + + assert user.info.note_count == 0 + + message = + @linkless_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + + test "it disallows posts with links" do + user = insert(:user) + + assert user.info.note_count == 0 + + message = + @linkful_message + |> Map.put("actor", user.ap_id) + + {:reject, _} = AntiLinkSpamPolicy.filter(message) + end + end + + describe "with old user" do + test "it allows posts without links" do + user = insert(:user, info: %{note_count: 1}) + + assert user.info.note_count == 1 + + message = + @linkless_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + + test "it allows posts with links" do + user = insert(:user, info: %{note_count: 1}) + + assert user.info.note_count == 1 + + message = + @linkful_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + end + + describe "with followed new user" do + test "it allows posts without links" do + user = insert(:user, info: %{follower_count: 1}) + + assert user.info.follower_count == 1 + + message = + @linkless_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + + test "it allows posts with links" do + user = insert(:user, info: %{follower_count: 1}) + + assert user.info.follower_count == 1 + + message = + @linkful_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + end + + describe "with unknown actors" do + test "it rejects posts without links" do + message = + @linkless_message + |> Map.put("actor", "http://invalid.actor") + + assert capture_log(fn -> + {:reject, _} = AntiLinkSpamPolicy.filter(message) + end) =~ "[error] Could not decode user at fetch http://invalid.actor" + end + + test "it rejects posts with links" do + message = + @linkful_message + |> Map.put("actor", "http://invalid.actor") + + assert capture_log(fn -> + {:reject, _} = AntiLinkSpamPolicy.filter(message) + end) =~ "[error] Could not decode user at fetch http://invalid.actor" + end + end + + describe "with contentless-objects" do + test "it does not reject them or error out" do + user = insert(:user, info: %{note_count: 1}) + + message = + @response_message + |> Map.put("actor", user.ap_id) + + {:ok, _message} = AntiLinkSpamPolicy.filter(message) + end + end +end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 18f64f2b7..4278ac59d 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1334,7 +1334,7 @@ test "with settings in db", %{conn: conn} do setup %{conn: conn} do admin = insert(:user, info: %{is_admin: true}) - temp_file = "config/test.migrated.secret.exs" + temp_file = "config/test.exported_from_db.secret.exs" on_exit(fn -> Application.delete_env(:pleroma, :key1) @@ -1343,6 +1343,8 @@ test "with settings in db", %{conn: conn} do Application.delete_env(:pleroma, :key4) Application.delete_env(:pleroma, :keyaa1) Application.delete_env(:pleroma, :keyaa2) + Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal) + Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) :ok = File.rm(temp_file) end) @@ -1361,8 +1363,9 @@ test "create new config setting in db", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ - %{key: "key1", value: "value1"}, + %{group: "pleroma", key: "key1", value: "value1"}, %{ + group: "pleroma", key: "key2", value: %{ "nested_1" => "nested_value1", @@ -1373,6 +1376,7 @@ test "create new config setting in db", %{conn: conn} do } }, %{ + group: "pleroma", key: "key3", value: [ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, @@ -1380,8 +1384,14 @@ test "create new config setting in db", %{conn: conn} do ] }, %{ + group: "pleroma", key: "key4", value: %{"nested_5" => ":upload", "endpoint" => "https://example.com"} + }, + %{ + group: "idna", + key: "key5", + value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} } ] }) @@ -1389,10 +1399,12 @@ test "create new config setting in db", %{conn: conn} do assert json_response(conn, 200) == %{ "configs" => [ %{ + "group" => "pleroma", "key" => "key1", "value" => "value1" }, %{ + "group" => "pleroma", "key" => "key2", "value" => [ %{"nested_1" => "nested_value1"}, @@ -1405,6 +1417,7 @@ test "create new config setting in db", %{conn: conn} do ] }, %{ + "group" => "pleroma", "key" => "key3", "value" => [ [%{"nested_3" => "nested_3"}, %{"nested_33" => "nested_33"}], @@ -1412,8 +1425,14 @@ test "create new config setting in db", %{conn: conn} do ] }, %{ + "group" => "pleroma", "key" => "key4", "value" => [%{"endpoint" => "https://example.com"}, %{"nested_5" => "upload"}] + }, + %{ + "group" => "idna", + "key" => "key5", + "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} } ] } @@ -1437,6 +1456,8 @@ test "create new config setting in db", %{conn: conn} do endpoint: "https://example.com", nested_5: :upload ] + + assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []} end test "update config setting & delete", %{conn: conn} do @@ -1446,14 +1467,15 @@ test "update config setting & delete", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ - %{key: config1.key, value: "another_value"}, - %{key: config2.key, delete: "true"} + %{group: config1.group, key: config1.key, value: "another_value"}, + %{group: config2.group, key: config2.key, delete: "true"} ] }) assert json_response(conn, 200) == %{ "configs" => [ %{ + "group" => "pleroma", "key" => config1.key, "value" => "another_value" } @@ -1463,5 +1485,152 @@ test "update config setting & delete", %{conn: conn} do assert Application.get_env(:pleroma, :keyaa1) == "another_value" refute Application.get_env(:pleroma, :keyaa2) end + + test "common config example", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + "group" => "pleroma", + "key" => "Pleroma.Captcha.NotReal", + "value" => %{ + "enabled" => ":false", + "method" => "Pleroma.Captcha.Kocaptcha", + "seconds_valid" => "i:60" + } + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => "pleroma", + "key" => "Pleroma.Captcha.NotReal", + "value" => [ + %{"enabled" => false}, + %{"method" => "Pleroma.Captcha.Kocaptcha"}, + %{"seconds_valid" => 60} + ] + } + ] + } + end + + test "tuples with more than two values", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + "group" => "pleroma", + "key" => "Pleroma.Web.Endpoint.NotReal", + "value" => [ + %{ + "http" => %{ + "dispatch" => [ + %{ + "tuple" => [ + ":_", + [ + %{ + "tuple" => [ + "/api/v1/streaming", + "Pleroma.Web.MastodonAPI.WebsocketHandler", + [] + ] + }, + %{ + "tuple" => [ + "/websocket", + "Phoenix.Endpoint.CowboyWebSocket", + %{ + "tuple" => [ + "Phoenix.Transports.WebSocket", + %{ + "tuple" => [ + "Pleroma.Web.Endpoint", + "Pleroma.Web.UserSocket", + [] + ] + } + ] + } + ] + }, + %{ + "tuple" => [ + ":_", + "Phoenix.Endpoint.Cowboy2Handler", + %{ + "tuple" => ["Pleroma.Web.Endpoint", []] + } + ] + } + ] + ] + } + ] + } + } + ] + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => "pleroma", + "key" => "Pleroma.Web.Endpoint.NotReal", + "value" => [ + %{ + "http" => %{ + "dispatch" => %{ + "_" => [ + %{ + "tuple" => [ + "/api/v1/streaming", + "Pleroma.Web.MastodonAPI.WebsocketHandler", + [] + ] + }, + %{ + "tuple" => [ + "/websocket", + "Phoenix.Endpoint.CowboyWebSocket", + %{ + "Elixir.Phoenix.Transports.WebSocket" => %{ + "tuple" => [ + "Pleroma.Web.Endpoint", + "Pleroma.Web.UserSocket", + [] + ] + } + } + ] + }, + %{ + "tuple" => [ + "_", + "Phoenix.Endpoint.Cowboy2Handler", + %{"Elixir.Pleroma.Web.Endpoint" => []} + ] + } + ] + } + } + } + ] + } + ] + } + end end end + +# Needed for testing +defmodule Pleroma.Web.Endpoint.NotReal do +end + +defmodule Pleroma.Captcha.NotReal do +end diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs index a2fedca40..10cb3b68a 100644 --- a/test/web/admin_api/config_test.exs +++ b/test/web/admin_api/config_test.exs @@ -7,18 +7,18 @@ test "get_by_key/1" do config = insert(:config) insert(:config) - assert config == Config.get_by_key(config.key) + assert config == Config.get_by_params(%{group: config.group, key: config.key}) end test "create/1" do - {:ok, config} = Config.create(%{key: "some_key", value: "some_value"}) - assert config == Config.get_by_key("some_key") + {:ok, config} = Config.create(%{group: "pleroma", key: "some_key", value: "some_value"}) + assert config == Config.get_by_params(%{group: "pleroma", key: "some_key"}) end test "update/1" do config = insert(:config) {:ok, updated} = Config.update(config, %{value: "some_value"}) - loaded = Config.get_by_key(config.key) + loaded = Config.get_by_params(%{group: config.group, key: config.key}) assert loaded == updated end @@ -27,8 +27,8 @@ test "update_or_create/1" do key2 = "another_key" params = [ - %{key: key2, value: "another_value"}, - %{key: config.key, value: "new_value"} + %{group: "pleroma", key: key2, value: "another_value"}, + %{group: config.group, key: config.key, value: "new_value"} ] assert Repo.all(Config) |> length() == 1 @@ -37,8 +37,8 @@ test "update_or_create/1" do assert Repo.all(Config) |> length() == 2 - config1 = Config.get_by_key(config.key) - config2 = Config.get_by_key(key2) + config1 = Config.get_by_params(%{group: config.group, key: config.key}) + config2 = Config.get_by_params(%{group: "pleroma", key: key2}) assert config1.value == Config.transform("new_value") assert config2.value == Config.transform("another_value") @@ -46,8 +46,8 @@ test "update_or_create/1" do test "delete/1" do config = insert(:config) - {:ok, _} = Config.delete(config.key) - refute Config.get_by_key(config.key) + {:ok, _} = Config.delete(%{key: config.key, group: config.group}) + refute Config.get_by_params(%{key: config.key, group: config.group}) end describe "transform/1" do @@ -179,5 +179,80 @@ test "complex map with sigil" do assert Config.from_binary(binary) == [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []] end + + test "complex map with tuples with more than 2 values" do + binary = + Config.transform(%{ + "http" => %{ + "dispatch" => [ + %{ + "tuple" => [ + ":_", + [ + %{ + "tuple" => [ + "/api/v1/streaming", + "Pleroma.Web.MastodonAPI.WebsocketHandler", + [] + ] + }, + %{ + "tuple" => [ + "/websocket", + "Phoenix.Endpoint.CowboyWebSocket", + %{ + "tuple" => [ + "Phoenix.Transports.WebSocket", + %{"tuple" => ["Pleroma.Web.Endpoint", "Pleroma.Web.UserSocket", []]} + ] + } + ] + }, + %{ + "tuple" => [ + ":_", + "Phoenix.Endpoint.Cowboy2Handler", + %{ + "tuple" => ["Pleroma.Web.Endpoint", []] + } + ] + } + ] + ] + } + ] + } + }) + + assert binary == + :erlang.term_to_binary( + http: [ + dispatch: [ + _: [ + {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, + {"/websocket", Phoenix.Endpoint.CowboyWebSocket, + {Phoenix.Transports.WebSocket, + {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, + {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} + ] + ] + ] + ) + + assert Config.from_binary(binary) == [ + http: [ + dispatch: [ + {:_, + [ + {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, + {"/websocket", Phoenix.Endpoint.CowboyWebSocket, + {Phoenix.Transports.WebSocket, + {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, + {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} + ]} + ] + ] + ] + end end end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index 242b7fdb3..aae34804d 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -10,6 +10,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.Token @oauth_config_path [:oauth2, :issue_new_refresh_token] @@ -49,7 +50,7 @@ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", % %{ "response_type" => "code", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "scope" => "read" } ) @@ -72,7 +73,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", % "authorization" => %{ "scope" => "read follow", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "a_state" } } @@ -98,11 +99,12 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", % test "with user-bound registration, GET /oauth//callback redirects to `redirect_uri` with `code`", %{app: app, conn: conn} do registration = insert(:registration) + redirect_uri = OAuthController.default_redirect_uri(app) state_params = %{ "scope" => Enum.join(app.scopes, " "), "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "" } @@ -121,7 +123,7 @@ test "with user-bound registration, GET /oauth//callback redirects to ) assert response = html_response(conn, 302) - assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ + assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ end end @@ -132,7 +134,7 @@ test "with user-unbound registration, GET /oauth//callback renders reg state_params = %{ "scope" => "read write", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "a_state" } @@ -165,7 +167,7 @@ test "on authentication error, GET /oauth//callback redirects to `redi state_params = %{ "scope" => Enum.join(app.scopes, " "), "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "" } @@ -199,7 +201,7 @@ test "GET /oauth/registration_details renders registration details form", %{ "authorization" => %{ "scopes" => app.scopes, "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "a_state", "nickname" => nil, "email" => "john@doe.com" @@ -218,6 +220,7 @@ test "with valid params, POST /oauth/register?op=register redirects to `redirect conn: conn } do registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) + redirect_uri = OAuthController.default_redirect_uri(app) conn = conn @@ -229,7 +232,7 @@ test "with valid params, POST /oauth/register?op=register redirects to `redirect "authorization" => %{ "scopes" => app.scopes, "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "a_state", "nickname" => "availablenick", "email" => "available@email.com" @@ -238,7 +241,36 @@ test "with valid params, POST /oauth/register?op=register redirects to `redirect ) assert response = html_response(conn, 302) - assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ + assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ + end + + test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401", + %{ + app: app, + conn: conn + } do + registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) + unlisted_redirect_uri = "http://cross-site-request.com" + + conn = + conn + |> put_session(:registration_id, registration.id) + |> post( + "/oauth/register", + %{ + "op" => "register", + "authorization" => %{ + "scopes" => app.scopes, + "client_id" => app.client_id, + "redirect_uri" => unlisted_redirect_uri, + "state" => "a_state", + "nickname" => "availablenick", + "email" => "available@email.com" + } + } + ) + + assert response = html_response(conn, 401) end test "with invalid params, POST /oauth/register?op=register renders registration_details page", @@ -254,7 +286,7 @@ test "with invalid params, POST /oauth/register?op=register renders registration "authorization" => %{ "scopes" => app.scopes, "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "a_state", "nickname" => "availablenickname", "email" => "available@email.com" @@ -286,6 +318,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_ } do user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword")) registration = insert(:registration, user: nil) + redirect_uri = OAuthController.default_redirect_uri(app) conn = conn @@ -297,7 +330,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_ "authorization" => %{ "scopes" => app.scopes, "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "a_state", "name" => user.nickname, "password" => "testpassword" @@ -306,7 +339,37 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_ ) assert response = html_response(conn, 302) - assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ + assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ + end + + test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`", + %{ + app: app, + conn: conn + } do + user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword")) + registration = insert(:registration, user: nil) + unlisted_redirect_uri = "http://cross-site-request.com" + + conn = + conn + |> put_session(:registration_id, registration.id) + |> post( + "/oauth/register", + %{ + "op" => "connect", + "authorization" => %{ + "scopes" => app.scopes, + "client_id" => app.client_id, + "redirect_uri" => unlisted_redirect_uri, + "state" => "a_state", + "name" => user.nickname, + "password" => "testpassword" + } + } + ) + + assert response = html_response(conn, 401) end test "with invalid params, POST /oauth/register?op=connect renders registration_details page", @@ -322,7 +385,7 @@ test "with invalid params, POST /oauth/register?op=connect renders registration_ "authorization" => %{ "scopes" => app.scopes, "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "a_state", "name" => user.nickname, "password" => "wrong password" @@ -358,7 +421,7 @@ test "renders authentication page", %{app: app, conn: conn} do %{ "response_type" => "code", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "scope" => "read" } ) @@ -378,7 +441,7 @@ test "properly handles internal calls with `authorization`-wrapped params", %{ "authorization" => %{ "response_type" => "code", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "scope" => "read" } } @@ -399,7 +462,7 @@ test "renders authentication page if user is already authenticated but `force_lo %{ "response_type" => "code", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "scope" => "read", "force_login" => "true" } @@ -423,7 +486,7 @@ test "with existing authentication and non-OOB `redirect_uri`, redirects to app %{ "response_type" => "code", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "state" => "specific_client_state", "scope" => "read" } @@ -433,6 +496,31 @@ test "with existing authentication and non-OOB `redirect_uri`, redirects to app "https://redirect.url?access_token=#{token.token}&state=specific_client_state" end + test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials", + %{ + app: app, + conn: conn + } do + unlisted_redirect_uri = "http://cross-site-request.com" + token = insert(:oauth_token, app_id: app.id) + + conn = + conn + |> put_session(:oauth_token, token.token) + |> get( + "/oauth/authorize", + %{ + "response_type" => "code", + "client_id" => app.client_id, + "redirect_uri" => unlisted_redirect_uri, + "state" => "specific_client_state", + "scope" => "read" + } + ) + + assert redirected_to(conn) == unlisted_redirect_uri + end + test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params", %{ app: app, @@ -461,6 +549,7 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with test "redirects with oauth authorization" do user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + redirect_uri = OAuthController.default_redirect_uri(app) conn = build_conn() @@ -469,14 +558,14 @@ test "redirects with oauth authorization" do "name" => user.nickname, "password" => "test", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "scope" => "read write", "state" => "statepassed" } }) target = redirected_to(conn) - assert target =~ app.redirect_uris + assert target =~ redirect_uri query = URI.parse(target).query |> URI.query_decoder() |> Map.new() @@ -489,6 +578,7 @@ test "redirects with oauth authorization" do test "returns 401 for wrong credentials", %{conn: conn} do user = insert(:user) app = insert(:oauth_app) + redirect_uri = OAuthController.default_redirect_uri(app) result = conn @@ -497,7 +587,7 @@ test "returns 401 for wrong credentials", %{conn: conn} do "name" => user.nickname, "password" => "wrong", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "statepassed", "scope" => Enum.join(app.scopes, " ") } @@ -506,7 +596,7 @@ test "returns 401 for wrong credentials", %{conn: conn} do # Keep the details assert result =~ app.client_id - assert result =~ app.redirect_uris + assert result =~ redirect_uri # Error message assert result =~ "Invalid Username/Password" @@ -515,6 +605,7 @@ test "returns 401 for wrong credentials", %{conn: conn} do test "returns 401 for missing scopes", %{conn: conn} do user = insert(:user) app = insert(:oauth_app) + redirect_uri = OAuthController.default_redirect_uri(app) result = conn @@ -523,7 +614,7 @@ test "returns 401 for missing scopes", %{conn: conn} do "name" => user.nickname, "password" => "test", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "statepassed", "scope" => "" } @@ -532,7 +623,7 @@ test "returns 401 for missing scopes", %{conn: conn} do # Keep the details assert result =~ app.client_id - assert result =~ app.redirect_uris + assert result =~ redirect_uri # Error message assert result =~ "This action is outside the authorized scopes" @@ -541,6 +632,7 @@ test "returns 401 for missing scopes", %{conn: conn} do test "returns 401 for scopes beyond app scopes", %{conn: conn} do user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write"]) + redirect_uri = OAuthController.default_redirect_uri(app) result = conn @@ -549,7 +641,7 @@ test "returns 401 for scopes beyond app scopes", %{conn: conn} do "name" => user.nickname, "password" => "test", "client_id" => app.client_id, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => redirect_uri, "state" => "statepassed", "scope" => "read write follow" } @@ -558,7 +650,7 @@ test "returns 401 for scopes beyond app scopes", %{conn: conn} do # Keep the details assert result =~ app.client_id - assert result =~ app.redirect_uris + assert result =~ redirect_uri # Error message assert result =~ "This action is outside the authorized scopes" @@ -577,7 +669,7 @@ test "issues a token for an all-body request" do |> post("/oauth/token", %{ "grant_type" => "authorization_code", "code" => auth.token, - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "client_id" => app.client_id, "client_secret" => app.client_secret }) @@ -631,7 +723,7 @@ test "issues a token for request with HTTP basic auth client credentials" do |> post("/oauth/token", %{ "grant_type" => "authorization_code", "code" => auth.token, - "redirect_uri" => app.redirect_uris + "redirect_uri" => OAuthController.default_redirect_uri(app) }) assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200) @@ -676,7 +768,7 @@ test "rejects token exchange with invalid client credentials" do |> post("/oauth/token", %{ "grant_type" => "authorization_code", "code" => auth.token, - "redirect_uri" => app.redirect_uris + "redirect_uri" => OAuthController.default_redirect_uri(app) }) assert resp = json_response(conn, 400) @@ -755,7 +847,7 @@ test "rejects an invalid authorization code" do |> post("/oauth/token", %{ "grant_type" => "authorization_code", "code" => "Imobviouslyinvalid", - "redirect_uri" => app.redirect_uris, + "redirect_uri" => OAuthController.default_redirect_uri(app), "client_id" => app.client_id, "client_secret" => app.client_secret }) diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index a49ba9549..bc48341ca 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -9,6 +9,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do } -> %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} + %{ + method: :get, + url: "http://example.com/non-ogp" + } -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")} + %{ method: :get, url: "http://example.com/ogp-missing-title" @@ -47,6 +53,11 @@ test "returns error when no metadata present" do assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/empty") end + test "doesn't just add a title" do + assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") == + {:error, "Found metadata was invalid or incomplete: %{}"} + end + test "parses ogp" do assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") == {:ok, diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs index 648e28712..4633d7765 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer_test.exs @@ -356,4 +356,110 @@ test "it doesn't send muted reblogs" do Task.await(task) end + + describe "direct streams" do + setup do + GenServer.start(Streamer, %{}, name: Streamer) + + on_exit(fn -> + if pid = Process.whereis(Streamer) do + Process.exit(pid, :kill) + end + end) + + :ok + end + + test "it sends conversation update to the 'direct' stream", %{} do + user = insert(:user) + another_user = insert(:user) + + task = + Task.async(fn -> + assert_receive {:text, _received_event}, 4_000 + end) + + Streamer.add_socket( + "direct", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, _create_activity} = + CommonAPI.post(another_user, %{ + "status" => "hey @#{user.nickname}", + "visibility" => "direct" + }) + + Task.await(task) + end + + test "it doesn't send conversation update to the 'direct' streamj when the last message in the conversation is deleted" do + user = insert(:user) + another_user = insert(:user) + + {:ok, create_activity} = + CommonAPI.post(another_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "direct" + }) + + task = + Task.async(fn -> + assert_receive {:text, received_event}, 4_000 + assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + + refute_receive {:text, _}, 4_000 + end) + + Streamer.add_socket( + "direct", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, _} = CommonAPI.delete(create_activity.id, another_user) + + Task.await(task) + end + + test "it sends conversation update to the 'direct' stream when a message is deleted" do + user = insert(:user) + another_user = insert(:user) + + {:ok, create_activity} = + CommonAPI.post(another_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "direct" + }) + + {:ok, create_activity2} = + CommonAPI.post(another_user, %{ + "status" => "hi @#{user.nickname}", + "in_reply_to_status_id" => create_activity.id, + "visibility" => "direct" + }) + + task = + Task.async(fn -> + assert_receive {:text, received_event}, 4_000 + assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + + assert_receive {:text, received_event}, 4_000 + + assert %{"event" => "conversation", "payload" => received_payload} = + Jason.decode!(received_event) + + assert %{"last_status" => last_status} = Jason.decode!(received_payload) + assert last_status["id"] == to_string(create_activity.id) + end) + + Streamer.add_socket( + "direct", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, _} = CommonAPI.delete(create_activity2.id, another_user) + + Task.await(task) + end + end end diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs new file mode 100644 index 000000000..6b9da8204 --- /dev/null +++ b/test/web/twitter_api/password_controller_test.exs @@ -0,0 +1,56 @@ +defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.PasswordResetToken + alias Pleroma.Web.OAuth.Token + import Pleroma.Factory + + describe "GET /api/pleroma/password_reset/token" do + test "it returns error when token invalid", %{conn: conn} do + response = + conn + |> get("/api/pleroma/password_reset/token") + |> html_response(:ok) + + assert response =~ "

Invalid Token

" + end + + test "it shows password reset form", %{conn: conn} do + user = insert(:user) + {:ok, token} = PasswordResetToken.create_token(user) + + response = + conn + |> get("/api/pleroma/password_reset/#{token.token}") + |> html_response(:ok) + + assert response =~ "

Password Reset for #{user.nickname}

" + end + end + + describe "POST /api/pleroma/password_reset" do + test "it returns HTTP 200", %{conn: conn} do + user = insert(:user) + {:ok, token} = PasswordResetToken.create_token(user) + {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + + params = %{ + "password" => "test", + password_confirmation: "test", + token: token.token + } + + response = + conn + |> assign(:user, user) + |> post("/api/pleroma/password_reset", %{data: params}) + |> html_response(:ok) + + assert response =~ "

Password changed!

" + + user = refresh_record(user) + assert Comeonin.Pbkdf2.checkpw("test", user.password_hash) + assert length(Token.get_user_tokens(user)) == 0 + end + end +end