Merge remote-tracking branch 'pleroma/develop' into use-jobs-in-webpush
This commit is contained in:
		
						commit
						9abf832b03
					
				
					 42 changed files with 1349 additions and 176 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -35,3 +35,6 @@ erl_crash.dump
 | 
			
		|||
 | 
			
		||||
# Editor config
 | 
			
		||||
/.vscode/
 | 
			
		||||
 | 
			
		||||
# Prevent committing docs files
 | 
			
		||||
/priv/static/doc/*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,7 +58,13 @@
 | 
			
		|||
  cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
 | 
			
		||||
  files: "https://mdii.sakura.ne.jp"
 | 
			
		||||
 | 
			
		||||
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
 | 
			
		||||
config :pleroma, :emoji,
 | 
			
		||||
  shortcode_globs: ["/emoji/custom/**/*.png"],
 | 
			
		||||
  groups: [
 | 
			
		||||
    # Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
 | 
			
		||||
    Finmoji: "/finmoji/128px/*-128.png",
 | 
			
		||||
    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
config :pleroma, :uri_schemes,
 | 
			
		||||
  valid_schemes: [
 | 
			
		||||
| 
						 | 
				
			
			@ -363,6 +369,7 @@
 | 
			
		|||
  federator_outgoing: 50,
 | 
			
		||||
  web_push: 50,
 | 
			
		||||
  mailer: 10,
 | 
			
		||||
  transmogrifier: 20,
 | 
			
		||||
  scheduled_activities: 10
 | 
			
		||||
 | 
			
		||||
config :pleroma, :fetch_initial_posts,
 | 
			
		||||
| 
						 | 
				
			
			@ -390,6 +397,22 @@
 | 
			
		|||
  base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
 | 
			
		||||
  uid: System.get_env("LDAP_UID") || "cn"
 | 
			
		||||
 | 
			
		||||
oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
 | 
			
		||||
 | 
			
		||||
ueberauth_providers =
 | 
			
		||||
  for strategy <- oauth_consumer_strategies do
 | 
			
		||||
    strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
 | 
			
		||||
    strategy_module = String.to_atom(strategy_module_name)
 | 
			
		||||
    {String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
config :ueberauth,
 | 
			
		||||
       Ueberauth,
 | 
			
		||||
       base_path: "/oauth",
 | 
			
		||||
       providers: ueberauth_providers
 | 
			
		||||
 | 
			
		||||
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
 | 
			
		||||
 | 
			
		||||
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
 | 
			
		||||
 | 
			
		||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@
 | 
			
		|||
    protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
 | 
			
		||||
  ],
 | 
			
		||||
  protocol: "http",
 | 
			
		||||
  secure_cookie_flag: false,
 | 
			
		||||
  debug_errors: true,
 | 
			
		||||
  code_reloader: true,
 | 
			
		||||
  check_origin: false,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
firefox, /emoji/Firefox.gif
 | 
			
		||||
blank, /emoji/blank.png
 | 
			
		||||
firefox, /emoji/Firefox.gif, Gif,Fun
 | 
			
		||||
blank, /emoji/blank.png, Fun
 | 
			
		||||
f_00b, /emoji/f_00b.png
 | 
			
		||||
f_00b11b, /emoji/f_00b11b.png
 | 
			
		||||
f_00b33b, /emoji/f_00b33b.png
 | 
			
		||||
| 
						 | 
				
			
			@ -28,4 +28,3 @@ f_33b00b, /emoji/f_33b00b.png
 | 
			
		|||
f_33b22b, /emoji/f_33b22b.png
 | 
			
		||||
f_33h, /emoji/f_33h.png
 | 
			
		||||
f_33t, /emoji/f_33t.png
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
 | 
			
		|||
* Authentication: not required
 | 
			
		||||
* Params: none
 | 
			
		||||
* Response: JSON
 | 
			
		||||
* Example response: `{"kalsarikannit_f":"/finmoji/128px/kalsarikannit_f-128.png","perkele":"/finmoji/128px/perkele-128.png","blobdab":"/emoji/blobdab.png","happiness":"/finmoji/128px/happiness-128.png"}`
 | 
			
		||||
* Example response: `[{"kalsarikannit_f":{"tags":["Finmoji"],"image_url":"/finmoji/128px/kalsarikannit_f-128.png"}},{"perkele":{"tags":["Finmoji"],"image_url":"/finmoji/128px/perkele-128.png"}},{"blobdab":{"tags":["SomeTag"],"image_url":"/emoji/blobdab.png"}},"happiness":{"tags":["Finmoji"],"image_url":"/finmoji/128px/happiness-128.png"}}]`
 | 
			
		||||
* Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
 | 
			
		||||
 | 
			
		||||
## `/api/pleroma/follow_import`
 | 
			
		||||
| 
						 | 
				
			
			@ -27,14 +27,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
 | 
			
		|||
* Method: `GET`
 | 
			
		||||
* Authentication: not required
 | 
			
		||||
* Params: none
 | 
			
		||||
* Response: Provider specific JSON, the only guaranteed parameter is `type` 
 | 
			
		||||
* Response: Provider specific JSON, the only guaranteed parameter is `type`
 | 
			
		||||
* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}`
 | 
			
		||||
 | 
			
		||||
## `/api/pleroma/delete_account`
 | 
			
		||||
### Delete an account
 | 
			
		||||
* Method `POST`
 | 
			
		||||
* Authentication: required
 | 
			
		||||
* Params: 
 | 
			
		||||
* Params:
 | 
			
		||||
    * `password`: user's password
 | 
			
		||||
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
 | 
			
		||||
* Example response: `{"error": "Invalid password."}`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -314,9 +314,11 @@ curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerando
 | 
			
		|||
[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
 | 
			
		||||
 | 
			
		||||
Pleroma has the following queues:
 | 
			
		||||
 | 
			
		||||
* `federator_outgoing` - Outgoing federation
 | 
			
		||||
* `federator_incoming` - Incoming federation
 | 
			
		||||
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
 | 
			
		||||
* `transmogrifier` - Transmogrifier
 | 
			
		||||
* `web_push` - Web push notifications
 | 
			
		||||
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -392,6 +394,17 @@ config :auto_linker,
 | 
			
		|||
  ]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Pleroma.ScheduledActivity
 | 
			
		||||
 | 
			
		||||
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
 | 
			
		||||
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
 | 
			
		||||
* `enabled`: whether scheduled activities are sent to the job queue to be executed
 | 
			
		||||
 | 
			
		||||
## Pleroma.Web.Auth.Authenticator
 | 
			
		||||
 | 
			
		||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
 | 
			
		||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
 | 
			
		||||
 | 
			
		||||
## :ldap
 | 
			
		||||
 | 
			
		||||
Use LDAP for user authentication.  When a user logs in to the Pleroma
 | 
			
		||||
| 
						 | 
				
			
			@ -410,13 +423,61 @@ Pleroma account will be created with the same name as the LDAP user name.
 | 
			
		|||
* `base`: LDAP base, e.g. "dc=example,dc=com"
 | 
			
		||||
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
 | 
			
		||||
 | 
			
		||||
## Pleroma.Web.Auth.Authenticator
 | 
			
		||||
## :auth
 | 
			
		||||
 | 
			
		||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
 | 
			
		||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
 | 
			
		||||
Authentication / authorization settings.
 | 
			
		||||
 | 
			
		||||
## Pleroma.ScheduledActivity
 | 
			
		||||
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`. 
 | 
			
		||||
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
 | 
			
		||||
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
 | 
			
		||||
 | 
			
		||||
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
 | 
			
		||||
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
 | 
			
		||||
* `enabled`: whether scheduled activities are sent to the job queue to be executed
 | 
			
		||||
# OAuth consumer mode
 | 
			
		||||
 | 
			
		||||
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
 | 
			
		||||
Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
 | 
			
		||||
 | 
			
		||||
Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,
 | 
			
		||||
e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.
 | 
			
		||||
The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
 | 
			
		||||
 | 
			
		||||
Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.  
 | 
			
		||||
 | 
			
		||||
* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback
 | 
			
		||||
 | 
			
		||||
* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/
 | 
			
		||||
 | 
			
		||||
* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback
 | 
			
		||||
 | 
			
		||||
* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback
 | 
			
		||||
 | 
			
		||||
Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
 | 
			
		||||
per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
# Twitter
 | 
			
		||||
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
 | 
			
		||||
  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
 | 
			
		||||
  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
 | 
			
		||||
 | 
			
		||||
# Facebook
 | 
			
		||||
config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
 | 
			
		||||
  client_id: System.get_env("FACEBOOK_APP_ID"),
 | 
			
		||||
  client_secret: System.get_env("FACEBOOK_APP_SECRET"),
 | 
			
		||||
  redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
 | 
			
		||||
 | 
			
		||||
# Google
 | 
			
		||||
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
 | 
			
		||||
  client_id: System.get_env("GOOGLE_CLIENT_ID"),
 | 
			
		||||
  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
 | 
			
		||||
  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
 | 
			
		||||
 | 
			
		||||
# Microsoft
 | 
			
		||||
config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
 | 
			
		||||
  client_id: System.get_env("MICROSOFT_CLIENT_ID"),
 | 
			
		||||
  client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
 | 
			
		||||
  
 | 
			
		||||
config :ueberauth, Ueberauth,
 | 
			
		||||
  providers: [
 | 
			
		||||
    microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
 | 
			
		||||
  ]
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,43 @@ image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
 | 
			
		|||
 | 
			
		||||
content of `config/custom_emoji.txt`:
 | 
			
		||||
```
 | 
			
		||||
happy, /emoji/custom/happy.png
 | 
			
		||||
sad, /emoji/custom/sad.png
 | 
			
		||||
happy, /emoji/custom/happy.png, Tag1,Tag2
 | 
			
		||||
sad, /emoji/custom/sad.png, Tag1
 | 
			
		||||
foo, /emoji/custom/foo.png
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
 | 
			
		||||
 | 
			
		||||
## Emoji tags (groups)
 | 
			
		||||
 | 
			
		||||
Default tags are set in `config.exs`.
 | 
			
		||||
```elixir
 | 
			
		||||
config :pleroma, :emoji,
 | 
			
		||||
  shortcode_globs: ["/emoji/custom/**/*.png"],
 | 
			
		||||
  groups: [
 | 
			
		||||
    Finmoji: "/finmoji/128px/*-128.png",
 | 
			
		||||
    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
 | 
			
		||||
  ]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Order of the `groups` matters, so to override default tags just put your group on top of the list. E.g:
 | 
			
		||||
```elixir
 | 
			
		||||
config :pleroma, :emoji,
 | 
			
		||||
  shortcode_globs: ["/emoji/custom/**/*.png"],
 | 
			
		||||
  groups: [
 | 
			
		||||
    "Finmoji special": "/finmoji/128px/a_trusted_friend-128.png", # special file
 | 
			
		||||
    "Cirno": "/emoji/custom/cirno*.png", # png files in /emoji/custom/ which start with `cirno`
 | 
			
		||||
    "Special group": "/emoji/custom/special_folder/*.png", # png files in /emoji/custom/special_folder/
 | 
			
		||||
    "Another group": "/emoji/custom/special_folder/*/.png", # png files in /emoji/custom/special_folder/ subfolders
 | 
			
		||||
    Finmoji: "/finmoji/128px/*-128.png",
 | 
			
		||||
    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
 | 
			
		||||
  ]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Priority of tags assigns in emoji.txt and custom.txt:
 | 
			
		||||
 | 
			
		||||
`tag in file > special group setting in config.exs > default setting in config.exs`
 | 
			
		||||
 | 
			
		||||
Priority for globs:
 | 
			
		||||
 | 
			
		||||
`special group setting in config.exs > default setting in config.exs`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,4 +57,8 @@ def delete([parent_key | keys]) do
 | 
			
		|||
  def delete(key) do
 | 
			
		||||
    Application.delete_env(:pleroma, key)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
 | 
			
		||||
 | 
			
		||||
  def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,13 +8,19 @@ defmodule Pleroma.Emoji do
 | 
			
		|||
 | 
			
		||||
    * the built-in Finmojis (if enabled in configuration),
 | 
			
		||||
    * the files: `config/emoji.txt` and `config/custom_emoji.txt`
 | 
			
		||||
    * glob paths
 | 
			
		||||
    * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
 | 
			
		||||
 | 
			
		||||
  This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
 | 
			
		||||
  """
 | 
			
		||||
  use GenServer
 | 
			
		||||
 | 
			
		||||
  @type pattern :: Regex.t() | module() | String.t()
 | 
			
		||||
  @type patterns :: pattern() | [pattern()]
 | 
			
		||||
  @type group_patterns :: keyword(patterns())
 | 
			
		||||
 | 
			
		||||
  @ets __MODULE__.Ets
 | 
			
		||||
  @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
 | 
			
		||||
  @groups Application.get_env(:pleroma, :emoji)[:groups]
 | 
			
		||||
 | 
			
		||||
  @doc false
 | 
			
		||||
  def start_link do
 | 
			
		||||
| 
						 | 
				
			
			@ -73,13 +79,14 @@ def code_change(_old_vsn, state, _extra) do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  defp load do
 | 
			
		||||
    finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
 | 
			
		||||
    shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
 | 
			
		||||
 | 
			
		||||
    emojis =
 | 
			
		||||
      (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
 | 
			
		||||
      (load_finmoji(finmoji_enabled) ++
 | 
			
		||||
         load_from_file("config/emoji.txt") ++
 | 
			
		||||
         load_from_file("config/custom_emoji.txt") ++
 | 
			
		||||
         load_from_globs(
 | 
			
		||||
           Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
 | 
			
		||||
         ))
 | 
			
		||||
         load_from_globs(shortcode_globs))
 | 
			
		||||
      |> Enum.reject(fn value -> value == nil end)
 | 
			
		||||
 | 
			
		||||
    true = :ets.insert(@ets, emojis)
 | 
			
		||||
| 
						 | 
				
			
			@ -151,9 +158,12 @@ defp load do
 | 
			
		|||
    "white_nights",
 | 
			
		||||
    "woollysocks"
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  defp load_finmoji(true) do
 | 
			
		||||
    Enum.map(@finmoji, fn finmoji ->
 | 
			
		||||
      {finmoji, "/finmoji/128px/#{finmoji}-128.png"}
 | 
			
		||||
      file_name = "/finmoji/128px/#{finmoji}-128.png"
 | 
			
		||||
      group = match_extra(@groups, file_name)
 | 
			
		||||
      {finmoji, file_name, to_string(group)}
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,8 +182,14 @@ defp load_from_file_stream(stream) do
 | 
			
		|||
    |> Stream.map(&String.trim/1)
 | 
			
		||||
    |> Stream.map(fn line ->
 | 
			
		||||
      case String.split(line, ~r/,\s*/) do
 | 
			
		||||
        [name, file] -> {name, file}
 | 
			
		||||
        _ -> nil
 | 
			
		||||
        [name, file, tags] ->
 | 
			
		||||
          {name, file, tags}
 | 
			
		||||
 | 
			
		||||
        [name, file] ->
 | 
			
		||||
          {name, file, to_string(match_extra(@groups, file))}
 | 
			
		||||
 | 
			
		||||
        _ ->
 | 
			
		||||
          nil
 | 
			
		||||
      end
 | 
			
		||||
    end)
 | 
			
		||||
    |> Enum.to_list()
 | 
			
		||||
| 
						 | 
				
			
			@ -190,9 +206,40 @@ defp load_from_globs(globs) do
 | 
			
		|||
      |> Enum.concat()
 | 
			
		||||
 | 
			
		||||
    Enum.map(paths, fn path ->
 | 
			
		||||
      tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
 | 
			
		||||
      shortcode = Path.basename(path, Path.extname(path))
 | 
			
		||||
      external_path = Path.join("/", Path.relative_to(path, static_path))
 | 
			
		||||
      {shortcode, external_path}
 | 
			
		||||
      {shortcode, external_path, to_string(tag)}
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  Finds a matching group for the given emoji filename
 | 
			
		||||
  """
 | 
			
		||||
  @spec match_extra(group_patterns(), String.t()) :: atom() | nil
 | 
			
		||||
  def match_extra(group_patterns, filename) do
 | 
			
		||||
    match_group_patterns(group_patterns, fn pattern ->
 | 
			
		||||
      case pattern do
 | 
			
		||||
        %Regex{} = regex -> Regex.match?(regex, filename)
 | 
			
		||||
        string when is_binary(string) -> filename == string
 | 
			
		||||
      end
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp match_group_patterns(group_patterns, matcher) do
 | 
			
		||||
    Enum.find_value(group_patterns, fn {group, patterns} ->
 | 
			
		||||
      patterns =
 | 
			
		||||
        patterns
 | 
			
		||||
        |> List.wrap()
 | 
			
		||||
        |> Enum.map(fn pattern ->
 | 
			
		||||
          if String.contains?(pattern, "*") do
 | 
			
		||||
            ~r(#{String.replace(pattern, "*", ".*")})
 | 
			
		||||
          else
 | 
			
		||||
            pattern
 | 
			
		||||
          end
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
      Enum.any?(patterns, matcher) && group
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,9 +77,9 @@ def emojify(text) do
 | 
			
		|||
  def emojify(text, nil), do: text
 | 
			
		||||
 | 
			
		||||
  def emojify(text, emoji, strip \\ false) do
 | 
			
		||||
    Enum.reduce(emoji, text, fn {emoji, file}, text ->
 | 
			
		||||
      emoji = HTML.strip_tags(emoji)
 | 
			
		||||
      file = HTML.strip_tags(file)
 | 
			
		||||
    Enum.reduce(emoji, text, fn emoji_data, text ->
 | 
			
		||||
      emoji = HTML.strip_tags(elem(emoji_data, 0))
 | 
			
		||||
      file = HTML.strip_tags(elem(emoji_data, 1))
 | 
			
		||||
 | 
			
		||||
      html =
 | 
			
		||||
        if not strip do
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ def demojify(text) do
 | 
			
		|||
  def demojify(text, nil), do: text
 | 
			
		||||
 | 
			
		||||
  def get_emoji(text) when is_binary(text) do
 | 
			
		||||
    Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
 | 
			
		||||
    Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_emoji(_), do: []
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										57
									
								
								lib/pleroma/registration.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								lib/pleroma/registration.ex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
# Pleroma: A lightweight social networking server
 | 
			
		||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 | 
			
		||||
# SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 | 
			
		||||
defmodule Pleroma.Registration do
 | 
			
		||||
  use Ecto.Schema
 | 
			
		||||
 | 
			
		||||
  import Ecto.Changeset
 | 
			
		||||
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
  alias Pleroma.User
 | 
			
		||||
 | 
			
		||||
  @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
 | 
			
		||||
 | 
			
		||||
  schema "registrations" do
 | 
			
		||||
    belongs_to(:user, User, type: Pleroma.FlakeId)
 | 
			
		||||
    field(:provider, :string)
 | 
			
		||||
    field(:uid, :string)
 | 
			
		||||
    field(:info, :map, default: %{})
 | 
			
		||||
 | 
			
		||||
    timestamps()
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def nickname(registration, default \\ nil),
 | 
			
		||||
    do: Map.get(registration.info, "nickname", default)
 | 
			
		||||
 | 
			
		||||
  def email(registration, default \\ nil),
 | 
			
		||||
    do: Map.get(registration.info, "email", default)
 | 
			
		||||
 | 
			
		||||
  def name(registration, default \\ nil),
 | 
			
		||||
    do: Map.get(registration.info, "name", default)
 | 
			
		||||
 | 
			
		||||
  def description(registration, default \\ nil),
 | 
			
		||||
    do: Map.get(registration.info, "description", default)
 | 
			
		||||
 | 
			
		||||
  def changeset(registration, params \\ %{}) do
 | 
			
		||||
    registration
 | 
			
		||||
    |> cast(params, [:user_id, :provider, :uid, :info])
 | 
			
		||||
    |> validate_required([:provider, :uid])
 | 
			
		||||
    |> foreign_key_constraint(:user_id)
 | 
			
		||||
    |> unique_constraint(:uid, name: :registrations_provider_uid_index)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def bind_to_user(registration, user) do
 | 
			
		||||
    registration
 | 
			
		||||
    |> changeset(%{user_id: (user && user.id) || nil})
 | 
			
		||||
    |> Repo.update()
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_by_provider_uid(provider, uid) do
 | 
			
		||||
    Repo.get_by(Registration,
 | 
			
		||||
      provider: to_string(provider),
 | 
			
		||||
      uid: to_string(uid)
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ defmodule Pleroma.User do
 | 
			
		|||
  alias Pleroma.Formatter
 | 
			
		||||
  alias Pleroma.Notification
 | 
			
		||||
  alias Pleroma.Object
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
  alias Pleroma.User
 | 
			
		||||
  alias Pleroma.Web
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +56,7 @@ defmodule Pleroma.User do
 | 
			
		|||
    field(:bookmarks, {:array, :string}, default: [])
 | 
			
		||||
    field(:last_refreshed_at, :naive_datetime_usec)
 | 
			
		||||
    has_many(:notifications, Notification)
 | 
			
		||||
    has_many(:registrations, Registration)
 | 
			
		||||
    embeds_one(:info, Pleroma.User.Info)
 | 
			
		||||
 | 
			
		||||
    timestamps()
 | 
			
		||||
| 
						 | 
				
			
			@ -216,7 +218,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
 | 
			
		|||
    changeset =
 | 
			
		||||
      struct
 | 
			
		||||
      |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
 | 
			
		||||
      |> validate_required([:email, :name, :nickname, :password, :password_confirmation])
 | 
			
		||||
      |> validate_required([:name, :nickname, :password, :password_confirmation])
 | 
			
		||||
      |> validate_confirmation(:password)
 | 
			
		||||
      |> unique_constraint(:email)
 | 
			
		||||
      |> unique_constraint(:nickname)
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +229,13 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
 | 
			
		|||
      |> validate_length(:name, min: 1, max: 100)
 | 
			
		||||
      |> put_change(:info, info_change)
 | 
			
		||||
 | 
			
		||||
    changeset =
 | 
			
		||||
      if opts[:external] do
 | 
			
		||||
        changeset
 | 
			
		||||
      else
 | 
			
		||||
        validate_required(changeset, [:email])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    if changeset.valid? do
 | 
			
		||||
      hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
 | 
			
		||||
      ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
 | 
			
		||||
| 
						 | 
				
			
			@ -505,11 +514,10 @@ def get_by_nickname(nickname) do
 | 
			
		|||
      end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_by_email(email), do: Repo.get_by(User, email: email)
 | 
			
		||||
 | 
			
		||||
  def get_by_nickname_or_email(nickname_or_email) do
 | 
			
		||||
    case user = Repo.get_by(User, nickname: nickname_or_email) do
 | 
			
		||||
      %User{} -> user
 | 
			
		||||
      nil -> Repo.get_by(User, email: nickname_or_email)
 | 
			
		||||
    end
 | 
			
		||||
    get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_cached_user_info(user) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -954,7 +954,7 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
 | 
			
		|||
 | 
			
		||||
  defp strip_internal_tags(object), do: object
 | 
			
		||||
 | 
			
		||||
  defp user_upgrade_task(user) do
 | 
			
		||||
  def perform(:user_upgrade, user) do
 | 
			
		||||
    # we pass a fake user so that the followers collection is stripped away
 | 
			
		||||
    old_follower_address = User.ap_followers(%User{nickname: user.nickname})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -999,28 +999,18 @@ defp user_upgrade_task(user) do
 | 
			
		|||
    Repo.update_all(q, [])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def upgrade_user_from_ap_id(ap_id, async \\ true) do
 | 
			
		||||
  def upgrade_user_from_ap_id(ap_id) do
 | 
			
		||||
    with %User{local: false} = user <- User.get_by_ap_id(ap_id),
 | 
			
		||||
         {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
 | 
			
		||||
      already_ap = User.ap_enabled?(user)
 | 
			
		||||
 | 
			
		||||
      {:ok, user} =
 | 
			
		||||
        User.upgrade_changeset(user, data)
 | 
			
		||||
        |> Repo.update()
 | 
			
		||||
 | 
			
		||||
      if !already_ap do
 | 
			
		||||
        # This could potentially take a long time, do it in the background
 | 
			
		||||
        if async do
 | 
			
		||||
          Task.start(fn ->
 | 
			
		||||
            user_upgrade_task(user)
 | 
			
		||||
          end)
 | 
			
		||||
        else
 | 
			
		||||
          user_upgrade_task(user)
 | 
			
		||||
        end
 | 
			
		||||
         {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
 | 
			
		||||
         already_ap <- User.ap_enabled?(user),
 | 
			
		||||
         {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
 | 
			
		||||
      unless already_ap do
 | 
			
		||||
        PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      {:ok, user}
 | 
			
		||||
    else
 | 
			
		||||
      %User{} = user -> {:ok, user}
 | 
			
		||||
      e -> e
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,7 +99,10 @@ def make_json_ld_header do
 | 
			
		|||
    %{
 | 
			
		||||
      "@context" => [
 | 
			
		||||
        "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
        "#{Web.base_url()}/schemas/litepub-0.1.jsonld"
 | 
			
		||||
        "#{Web.base_url()}/schemas/litepub-0.1.jsonld",
 | 
			
		||||
        %{
 | 
			
		||||
          "@language" => "und"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
# SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 | 
			
		||||
defmodule Pleroma.Web.Auth.Authenticator do
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.User
 | 
			
		||||
 | 
			
		||||
  def implementation do
 | 
			
		||||
| 
						 | 
				
			
			@ -12,14 +13,33 @@ def implementation do
 | 
			
		|||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
 | 
			
		||||
  def get_user(plug), do: implementation().get_user(plug)
 | 
			
		||||
  @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
 | 
			
		||||
  def get_user(plug, params), do: implementation().get_user(plug, params)
 | 
			
		||||
 | 
			
		||||
  @callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
 | 
			
		||||
              {:ok, User.t()} | {:error, any()}
 | 
			
		||||
  def create_from_registration(plug, params, registration),
 | 
			
		||||
    do: implementation().create_from_registration(plug, params, registration)
 | 
			
		||||
 | 
			
		||||
  @callback get_registration(Plug.Conn.t(), Map.t()) ::
 | 
			
		||||
              {:ok, Registration.t()} | {:error, any()}
 | 
			
		||||
  def get_registration(plug, params),
 | 
			
		||||
    do: implementation().get_registration(plug, params)
 | 
			
		||||
 | 
			
		||||
  @callback handle_error(Plug.Conn.t(), any()) :: any()
 | 
			
		||||
  def handle_error(plug, error), do: implementation().handle_error(plug, error)
 | 
			
		||||
 | 
			
		||||
  @callback auth_template() :: String.t() | nil
 | 
			
		||||
  def auth_template do
 | 
			
		||||
    implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
 | 
			
		||||
    # Note: `config :pleroma, :auth_template, "..."` support is deprecated
 | 
			
		||||
    implementation().auth_template() ||
 | 
			
		||||
      Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
 | 
			
		||||
      "show.html"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @callback oauth_consumer_template() :: String.t() | nil
 | 
			
		||||
  def oauth_consumer_template do
 | 
			
		||||
    implementation().oauth_consumer_template() ||
 | 
			
		||||
      Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,14 +8,19 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
 | 
			
		|||
  require Logger
 | 
			
		||||
 | 
			
		||||
  @behaviour Pleroma.Web.Auth.Authenticator
 | 
			
		||||
  @base Pleroma.Web.Auth.PleromaAuthenticator
 | 
			
		||||
 | 
			
		||||
  @connection_timeout 10_000
 | 
			
		||||
  @search_timeout 10_000
 | 
			
		||||
 | 
			
		||||
  def get_user(%Plug.Conn{} = conn) do
 | 
			
		||||
  defdelegate get_registration(conn, params), to: @base
 | 
			
		||||
 | 
			
		||||
  defdelegate create_from_registration(conn, params, registration), to: @base
 | 
			
		||||
 | 
			
		||||
  def get_user(%Plug.Conn{} = conn, params) do
 | 
			
		||||
    if Pleroma.Config.get([:ldap, :enabled]) do
 | 
			
		||||
      {name, password} =
 | 
			
		||||
        case conn.params do
 | 
			
		||||
        case params do
 | 
			
		||||
          %{"authorization" => %{"name" => name, "password" => password}} ->
 | 
			
		||||
            {name, password}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,14 +34,14 @@ def get_user(%Plug.Conn{} = conn) do
 | 
			
		|||
 | 
			
		||||
        {:error, {:ldap_connection_error, _}} ->
 | 
			
		||||
          # When LDAP is unavailable, try default authenticator
 | 
			
		||||
          Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
 | 
			
		||||
          @base.get_user(conn, params)
 | 
			
		||||
 | 
			
		||||
        error ->
 | 
			
		||||
          error
 | 
			
		||||
      end
 | 
			
		||||
    else
 | 
			
		||||
      # Fall back to default authenticator
 | 
			
		||||
      Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
 | 
			
		||||
      @base.get_user(conn, params)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +51,8 @@ def handle_error(%Plug.Conn{} = _conn, error) do
 | 
			
		|||
 | 
			
		||||
  def auth_template, do: nil
 | 
			
		||||
 | 
			
		||||
  def oauth_consumer_template, do: nil
 | 
			
		||||
 | 
			
		||||
  defp ldap_user(name, password) do
 | 
			
		||||
    ldap = Pleroma.Config.get(:ldap, [])
 | 
			
		||||
    host = Keyword.get(ldap, :host, "localhost")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,13 +4,15 @@
 | 
			
		|||
 | 
			
		||||
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
 | 
			
		||||
  alias Comeonin.Pbkdf2
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
  alias Pleroma.User
 | 
			
		||||
 | 
			
		||||
  @behaviour Pleroma.Web.Auth.Authenticator
 | 
			
		||||
 | 
			
		||||
  def get_user(%Plug.Conn{} = conn) do
 | 
			
		||||
  def get_user(%Plug.Conn{} = _conn, params) do
 | 
			
		||||
    {name, password} =
 | 
			
		||||
      case conn.params do
 | 
			
		||||
      case params do
 | 
			
		||||
        %{"authorization" => %{"name" => name, "password" => password}} ->
 | 
			
		||||
          {name, password}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,9 +29,69 @@ def get_user(%Plug.Conn{} = conn) do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_registration(
 | 
			
		||||
        %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
 | 
			
		||||
        _params
 | 
			
		||||
      ) do
 | 
			
		||||
    registration = Registration.get_by_provider_uid(provider, uid)
 | 
			
		||||
 | 
			
		||||
    if registration do
 | 
			
		||||
      {:ok, registration}
 | 
			
		||||
    else
 | 
			
		||||
      info = auth.info
 | 
			
		||||
 | 
			
		||||
      Registration.changeset(%Registration{}, %{
 | 
			
		||||
        provider: to_string(provider),
 | 
			
		||||
        uid: to_string(uid),
 | 
			
		||||
        info: %{
 | 
			
		||||
          "nickname" => info.nickname,
 | 
			
		||||
          "email" => info.email,
 | 
			
		||||
          "name" => info.name,
 | 
			
		||||
          "description" => info.description
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      |> Repo.insert()
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
 | 
			
		||||
 | 
			
		||||
  def create_from_registration(_conn, params, registration) do
 | 
			
		||||
    nickname = value([params["nickname"], Registration.nickname(registration)])
 | 
			
		||||
    email = value([params["email"], Registration.email(registration)])
 | 
			
		||||
    name = value([params["name"], Registration.name(registration)]) || nickname
 | 
			
		||||
    bio = value([params["bio"], Registration.description(registration)])
 | 
			
		||||
 | 
			
		||||
    random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
 | 
			
		||||
 | 
			
		||||
    with {:ok, new_user} <-
 | 
			
		||||
           User.register_changeset(
 | 
			
		||||
             %User{},
 | 
			
		||||
             %{
 | 
			
		||||
               email: email,
 | 
			
		||||
               nickname: nickname,
 | 
			
		||||
               name: name,
 | 
			
		||||
               bio: bio,
 | 
			
		||||
               password: random_password,
 | 
			
		||||
               password_confirmation: random_password
 | 
			
		||||
             },
 | 
			
		||||
             external: true,
 | 
			
		||||
             confirmed: true
 | 
			
		||||
           )
 | 
			
		||||
           |> Repo.insert(),
 | 
			
		||||
         {:ok, _} <-
 | 
			
		||||
           Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
 | 
			
		||||
      {:ok, new_user}
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
 | 
			
		||||
 | 
			
		||||
  def handle_error(%Plug.Conn{} = _conn, error) do
 | 
			
		||||
    error
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def auth_template, do: nil
 | 
			
		||||
 | 
			
		||||
  def oauth_consumer_template, do: nil
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ def post(user, %{"status" => status} = data) do
 | 
			
		|||
             object,
 | 
			
		||||
             "emoji",
 | 
			
		||||
             (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
 | 
			
		||||
             |> Enum.reduce(%{}, fn {name, file}, acc ->
 | 
			
		||||
             |> Enum.reduce(%{}, fn {name, file, _}, acc ->
 | 
			
		||||
               Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
 | 
			
		||||
             end)
 | 
			
		||||
           ) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -293,7 +293,7 @@ def confirm_current_password(user, password) do
 | 
			
		|||
 | 
			
		||||
  def emoji_from_profile(%{info: _info} = user) do
 | 
			
		||||
    (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
 | 
			
		||||
    |> Enum.map(fn {shortcode, url} ->
 | 
			
		||||
    |> Enum.map(fn {shortcode, url, _} ->
 | 
			
		||||
      %{
 | 
			
		||||
        "type" => "Emoji",
 | 
			
		||||
        "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,11 +51,22 @@ defmodule Pleroma.Web.Endpoint do
 | 
			
		|||
  plug(Plug.MethodOverride)
 | 
			
		||||
  plug(Plug.Head)
 | 
			
		||||
 | 
			
		||||
  secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
 | 
			
		||||
 | 
			
		||||
  cookie_name =
 | 
			
		||||
    if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
 | 
			
		||||
    if secure_cookies,
 | 
			
		||||
      do: "__Host-pleroma_key",
 | 
			
		||||
      else: "pleroma_key"
 | 
			
		||||
 | 
			
		||||
  same_site =
 | 
			
		||||
    if Pleroma.Config.oauth_consumer_enabled?() do
 | 
			
		||||
      # Note: "SameSite=Strict" prevents sign in with external OAuth provider
 | 
			
		||||
      #   (there would be no cookies during callback request from OAuth provider)
 | 
			
		||||
      "SameSite=Lax"
 | 
			
		||||
    else
 | 
			
		||||
      "SameSite=Strict"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  # The session will be stored in the cookie and signed,
 | 
			
		||||
  # this means its contents can be read but not tampered with.
 | 
			
		||||
  # Set :encryption_salt if you would also like to encrypt it.
 | 
			
		||||
| 
						 | 
				
			
			@ -65,9 +76,8 @@ defmodule Pleroma.Web.Endpoint do
 | 
			
		|||
    key: cookie_name,
 | 
			
		||||
    signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
 | 
			
		||||
    http_only: true,
 | 
			
		||||
    secure:
 | 
			
		||||
      Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
 | 
			
		||||
    extra: "SameSite=Strict"
 | 
			
		||||
    secure: secure_cookies,
 | 
			
		||||
    extra: same_site
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  # Note: the plug and its configuration is compile-time this can't be upstreamed yet
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -181,14 +181,15 @@ def peers(conn, _params) do
 | 
			
		|||
 | 
			
		||||
  defp mastodonized_emoji do
 | 
			
		||||
    Pleroma.Emoji.get_all()
 | 
			
		||||
    |> Enum.map(fn {shortcode, relative_url} ->
 | 
			
		||||
    |> Enum.map(fn {shortcode, relative_url, tags} ->
 | 
			
		||||
      url = to_string(URI.merge(Web.base_url(), relative_url))
 | 
			
		||||
 | 
			
		||||
      %{
 | 
			
		||||
        "shortcode" => shortcode,
 | 
			
		||||
        "static_url" => url,
 | 
			
		||||
        "visible_in_picker" => true,
 | 
			
		||||
        "url" => url
 | 
			
		||||
        "url" => url,
 | 
			
		||||
        "tags" => String.split(tags, ",")
 | 
			
		||||
      }
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
 | 
			
		|||
  use Pleroma.Web, :controller
 | 
			
		||||
  alias Pleroma.Web.OAuth.OAuthController
 | 
			
		||||
 | 
			
		||||
  # No user/password
 | 
			
		||||
  def call(conn, _) do
 | 
			
		||||
  def call(conn, {:register, :generic_error}) do
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_status(:internal_server_error)
 | 
			
		||||
    |> put_flash(:error, "Unknown error, please check the details and try again.")
 | 
			
		||||
    |> OAuthController.registration_details(conn.params)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def call(conn, {:register, _error}) do
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_status(:unauthorized)
 | 
			
		||||
    |> put_flash(:error, "Invalid Username/Password")
 | 
			
		||||
    |> OAuthController.registration_details(conn.params)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def call(conn, _error) do
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_status(:unauthorized)
 | 
			
		||||
    |> put_flash(:error, "Invalid Username/Password")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
defmodule Pleroma.Web.OAuth.OAuthController do
 | 
			
		||||
  use Pleroma.Web, :controller
 | 
			
		||||
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
  alias Pleroma.User
 | 
			
		||||
  alias Pleroma.Web.Auth.Authenticator
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +16,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 | 
			
		|||
 | 
			
		||||
  import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
 | 
			
		||||
 | 
			
		||||
  if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
 | 
			
		||||
 | 
			
		||||
  plug(:fetch_session)
 | 
			
		||||
  plug(:fetch_flash)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,68 +60,67 @@ defp do_authorize(conn, params) do
 | 
			
		|||
    })
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_authorization(conn, %{
 | 
			
		||||
        "authorization" =>
 | 
			
		||||
          %{
 | 
			
		||||
            "client_id" => client_id,
 | 
			
		||||
            "redirect_uri" => redirect_uri
 | 
			
		||||
          } = auth_params
 | 
			
		||||
      }) do
 | 
			
		||||
    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
 | 
			
		||||
         %App{} = app <- Repo.get_by(App, client_id: client_id),
 | 
			
		||||
         true <- redirect_uri in String.split(app.redirect_uris),
 | 
			
		||||
         scopes <- oauth_scopes(auth_params, []),
 | 
			
		||||
         {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
 | 
			
		||||
         # Note: `scope` param is intentionally not optional in this context
 | 
			
		||||
         {:missing_scopes, false} <- {:missing_scopes, scopes == []},
 | 
			
		||||
         {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
 | 
			
		||||
         {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
 | 
			
		||||
      redirect_uri = redirect_uri(conn, redirect_uri)
 | 
			
		||||
 | 
			
		||||
      cond do
 | 
			
		||||
        redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
 | 
			
		||||
          render(conn, "results.html", %{
 | 
			
		||||
            auth: auth
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
        true ->
 | 
			
		||||
          connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
 | 
			
		||||
          url = "#{redirect_uri}#{connector}"
 | 
			
		||||
          url_params = %{:code => auth.token}
 | 
			
		||||
 | 
			
		||||
          url_params =
 | 
			
		||||
            if auth_params["state"] do
 | 
			
		||||
              Map.put(url_params, :state, auth_params["state"])
 | 
			
		||||
            else
 | 
			
		||||
              url_params
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
          url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
 | 
			
		||||
 | 
			
		||||
          redirect(conn, external: url)
 | 
			
		||||
      end
 | 
			
		||||
  def create_authorization(
 | 
			
		||||
        conn,
 | 
			
		||||
        %{"authorization" => auth_params} = params,
 | 
			
		||||
        opts \\ []
 | 
			
		||||
      ) do
 | 
			
		||||
    with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
 | 
			
		||||
      after_create_authorization(conn, auth, auth_params)
 | 
			
		||||
    else
 | 
			
		||||
      {scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
 | 
			
		||||
        # Per https://github.com/tootsuite/mastodon/blob/
 | 
			
		||||
        #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_flash(:error, "This action is outside the authorized scopes")
 | 
			
		||||
        |> put_status(:unauthorized)
 | 
			
		||||
        |> authorize(auth_params)
 | 
			
		||||
 | 
			
		||||
      {:auth_active, false} ->
 | 
			
		||||
        # Per https://github.com/tootsuite/mastodon/blob/
 | 
			
		||||
        #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_flash(:error, "Your login is missing a confirmed e-mail address")
 | 
			
		||||
        |> put_status(:forbidden)
 | 
			
		||||
        |> authorize(auth_params)
 | 
			
		||||
 | 
			
		||||
      error ->
 | 
			
		||||
        Authenticator.handle_error(conn, error)
 | 
			
		||||
        handle_create_authorization_error(conn, error, auth_params)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
 | 
			
		||||
    redirect_uri = redirect_uri(conn, redirect_uri)
 | 
			
		||||
 | 
			
		||||
    if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
 | 
			
		||||
      render(conn, "results.html", %{
 | 
			
		||||
        auth: auth
 | 
			
		||||
      })
 | 
			
		||||
    else
 | 
			
		||||
      connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
 | 
			
		||||
      url = "#{redirect_uri}#{connector}"
 | 
			
		||||
      url_params = %{:code => auth.token}
 | 
			
		||||
 | 
			
		||||
      url_params =
 | 
			
		||||
        if auth_params["state"] do
 | 
			
		||||
          Map.put(url_params, :state, auth_params["state"])
 | 
			
		||||
        else
 | 
			
		||||
          url_params
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
 | 
			
		||||
 | 
			
		||||
      redirect(conn, external: url)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
 | 
			
		||||
       when scopes_issue in [:unsupported_scopes, :missing_scopes] do
 | 
			
		||||
    # Per https://github.com/tootsuite/mastodon/blob/
 | 
			
		||||
    #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_flash(:error, "This action is outside the authorized scopes")
 | 
			
		||||
    |> put_status(:unauthorized)
 | 
			
		||||
    |> authorize(auth_params)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
 | 
			
		||||
    # Per https://github.com/tootsuite/mastodon/blob/
 | 
			
		||||
    #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_flash(:error, "Your login is missing a confirmed e-mail address")
 | 
			
		||||
    |> put_status(:forbidden)
 | 
			
		||||
    |> authorize(auth_params)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp handle_create_authorization_error(conn, error, _auth_params) do
 | 
			
		||||
    Authenticator.handle_error(conn, error)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
 | 
			
		||||
    with %App{} = app <- get_app_from_request(conn, params),
 | 
			
		||||
         fixed_token = fix_padding(params["code"]),
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +151,7 @@ def token_exchange(
 | 
			
		|||
        conn,
 | 
			
		||||
        %{"grant_type" => "password"} = params
 | 
			
		||||
      ) do
 | 
			
		||||
    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
 | 
			
		||||
    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
 | 
			
		||||
         %App{} = app <- get_app_from_request(conn, params),
 | 
			
		||||
         {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
 | 
			
		||||
         {:user_active, true} <- {:user_active, !user.info.deactivated},
 | 
			
		||||
| 
						 | 
				
			
			@ -211,6 +213,184 @@ def token_revoke(conn, %{"token" => token} = params) do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @doc "Prepares OAuth request to provider for Ueberauth"
 | 
			
		||||
  def prepare_request(conn, %{"provider" => provider} = params) do
 | 
			
		||||
    scope =
 | 
			
		||||
      oauth_scopes(params, [])
 | 
			
		||||
      |> Enum.join(" ")
 | 
			
		||||
 | 
			
		||||
    state =
 | 
			
		||||
      params
 | 
			
		||||
      |> Map.delete("scopes")
 | 
			
		||||
      |> Map.put("scope", scope)
 | 
			
		||||
      |> Poison.encode!()
 | 
			
		||||
 | 
			
		||||
    params =
 | 
			
		||||
      params
 | 
			
		||||
      |> Map.drop(~w(scope scopes client_id redirect_uri))
 | 
			
		||||
      |> Map.put("state", state)
 | 
			
		||||
 | 
			
		||||
    # Handing the request to Ueberauth
 | 
			
		||||
    redirect(conn, to: o_auth_path(conn, :request, provider, params))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def request(conn, params) do
 | 
			
		||||
    message =
 | 
			
		||||
      if params["provider"] do
 | 
			
		||||
        "Unsupported OAuth provider: #{params["provider"]}."
 | 
			
		||||
      else
 | 
			
		||||
        "Bad OAuth request."
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_flash(:error, message)
 | 
			
		||||
    |> redirect(to: "/")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
 | 
			
		||||
    params = callback_params(params)
 | 
			
		||||
    messages = for e <- Map.get(failure, :errors, []), do: e.message
 | 
			
		||||
    message = Enum.join(messages, "; ")
 | 
			
		||||
 | 
			
		||||
    conn
 | 
			
		||||
    |> put_flash(:error, "Failed to authenticate: #{message}.")
 | 
			
		||||
    |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def callback(conn, params) do
 | 
			
		||||
    params = callback_params(params)
 | 
			
		||||
 | 
			
		||||
    with {:ok, registration} <- Authenticator.get_registration(conn, params) do
 | 
			
		||||
      user = Repo.preload(registration, :user).user
 | 
			
		||||
      auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
 | 
			
		||||
 | 
			
		||||
      if user do
 | 
			
		||||
        create_authorization(
 | 
			
		||||
          conn,
 | 
			
		||||
          %{"authorization" => auth_params},
 | 
			
		||||
          user: user
 | 
			
		||||
        )
 | 
			
		||||
      else
 | 
			
		||||
        registration_params =
 | 
			
		||||
          Map.merge(auth_params, %{
 | 
			
		||||
            "nickname" => Registration.nickname(registration),
 | 
			
		||||
            "email" => Registration.email(registration)
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_session(:registration_id, registration.id)
 | 
			
		||||
        |> registration_details(registration_params)
 | 
			
		||||
      end
 | 
			
		||||
    else
 | 
			
		||||
      _ ->
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_flash(:error, "Failed to set up user account.")
 | 
			
		||||
        |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp callback_params(%{"state" => state} = params) do
 | 
			
		||||
    Map.merge(params, Poison.decode!(state))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def registration_details(conn, params) do
 | 
			
		||||
    render(conn, "register.html", %{
 | 
			
		||||
      client_id: params["client_id"],
 | 
			
		||||
      redirect_uri: params["redirect_uri"],
 | 
			
		||||
      state: params["state"],
 | 
			
		||||
      scopes: oauth_scopes(params, []),
 | 
			
		||||
      nickname: params["nickname"],
 | 
			
		||||
      email: params["email"]
 | 
			
		||||
    })
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def register(conn, %{"op" => "connect"} = params) do
 | 
			
		||||
    authorization_params = Map.put(params, "name", params["auth_name"])
 | 
			
		||||
    create_authorization_params = %{"authorization" => authorization_params}
 | 
			
		||||
 | 
			
		||||
    with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
 | 
			
		||||
         %Registration{} = registration <- Repo.get(Registration, registration_id),
 | 
			
		||||
         {_, {:ok, auth}} <-
 | 
			
		||||
           {:create_authorization, do_create_authorization(conn, create_authorization_params)},
 | 
			
		||||
         %User{} = user <- Repo.preload(auth, :user).user,
 | 
			
		||||
         {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
 | 
			
		||||
      conn
 | 
			
		||||
      |> put_session_registration_id(nil)
 | 
			
		||||
      |> after_create_authorization(auth, authorization_params)
 | 
			
		||||
    else
 | 
			
		||||
      {:create_authorization, error} ->
 | 
			
		||||
        {:register, handle_create_authorization_error(conn, error, create_authorization_params)}
 | 
			
		||||
 | 
			
		||||
      _ ->
 | 
			
		||||
        {:register, :generic_error}
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def register(conn, %{"op" => "register"} = params) do
 | 
			
		||||
    with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
 | 
			
		||||
         %Registration{} = registration <- Repo.get(Registration, registration_id),
 | 
			
		||||
         {:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
 | 
			
		||||
      conn
 | 
			
		||||
      |> put_session_registration_id(nil)
 | 
			
		||||
      |> create_authorization(
 | 
			
		||||
        %{
 | 
			
		||||
          "authorization" => %{
 | 
			
		||||
            "client_id" => params["client_id"],
 | 
			
		||||
            "redirect_uri" => params["redirect_uri"],
 | 
			
		||||
            "scopes" => oauth_scopes(params, nil)
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        user: user
 | 
			
		||||
      )
 | 
			
		||||
    else
 | 
			
		||||
      {:error, changeset} ->
 | 
			
		||||
        message =
 | 
			
		||||
          Enum.map(changeset.errors, fn {field, {error, _}} ->
 | 
			
		||||
            "#{field} #{error}"
 | 
			
		||||
          end)
 | 
			
		||||
          |> Enum.join("; ")
 | 
			
		||||
 | 
			
		||||
        message =
 | 
			
		||||
          String.replace(
 | 
			
		||||
            message,
 | 
			
		||||
            "ap_id has already been taken",
 | 
			
		||||
            "nickname has already been taken"
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_status(:forbidden)
 | 
			
		||||
        |> put_flash(:error, "Error: #{message}.")
 | 
			
		||||
        |> registration_details(params)
 | 
			
		||||
 | 
			
		||||
      _ ->
 | 
			
		||||
        {:register, :generic_error}
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp do_create_authorization(
 | 
			
		||||
         conn,
 | 
			
		||||
         %{
 | 
			
		||||
           "authorization" =>
 | 
			
		||||
             %{
 | 
			
		||||
               "client_id" => client_id,
 | 
			
		||||
               "redirect_uri" => redirect_uri
 | 
			
		||||
             } = auth_params
 | 
			
		||||
         } = params,
 | 
			
		||||
         user \\ nil
 | 
			
		||||
       ) do
 | 
			
		||||
    with {_, {:ok, %User{} = user}} <-
 | 
			
		||||
           {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
 | 
			
		||||
         %App{} = app <- Repo.get_by(App, client_id: client_id),
 | 
			
		||||
         true <- redirect_uri in String.split(app.redirect_uris),
 | 
			
		||||
         scopes <- oauth_scopes(auth_params, []),
 | 
			
		||||
         {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
 | 
			
		||||
         # Note: `scope` param is intentionally not optional in this context
 | 
			
		||||
         {:missing_scopes, false} <- {:missing_scopes, scopes == []},
 | 
			
		||||
         {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
 | 
			
		||||
      Authorization.create_authorization(app, user, scopes)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
 | 
			
		||||
  # decoding it.  Investigate sometime.
 | 
			
		||||
  defp fix_padding(token) do
 | 
			
		||||
| 
						 | 
				
			
			@ -248,4 +428,9 @@ defp get_app_from_request(conn, params) do
 | 
			
		|||
  defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
 | 
			
		||||
 | 
			
		||||
  defp redirect_uri(_conn, redirect_uri), do: redirect_uri
 | 
			
		||||
 | 
			
		||||
  defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
 | 
			
		||||
 | 
			
		||||
  defp put_session_registration_id(conn, registration_id),
 | 
			
		||||
    do: put_session(conn, :registration_id, registration_id)
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,11 @@
 | 
			
		|||
defmodule Pleroma.Web.Router do
 | 
			
		||||
  use Pleroma.Web, :router
 | 
			
		||||
 | 
			
		||||
  pipeline :browser do
 | 
			
		||||
    plug(:accepts, ["html"])
 | 
			
		||||
    plug(:fetch_session)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  pipeline :oauth do
 | 
			
		||||
    plug(:fetch_session)
 | 
			
		||||
    plug(Pleroma.Plugs.OAuthPlug)
 | 
			
		||||
| 
						 | 
				
			
			@ -213,6 +218,16 @@ defmodule Pleroma.Web.Router do
 | 
			
		|||
    post("/authorize", OAuthController, :create_authorization)
 | 
			
		||||
    post("/token", OAuthController, :token_exchange)
 | 
			
		||||
    post("/revoke", OAuthController, :token_revoke)
 | 
			
		||||
    get("/registration_details", OAuthController, :registration_details)
 | 
			
		||||
 | 
			
		||||
    scope [] do
 | 
			
		||||
      pipe_through(:browser)
 | 
			
		||||
 | 
			
		||||
      get("/prepare_request", OAuthController, :prepare_request)
 | 
			
		||||
      get("/:provider", OAuthController, :request)
 | 
			
		||||
      get("/:provider/callback", OAuthController, :callback)
 | 
			
		||||
      post("/register", OAuthController, :register)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  scope "/api/v1", Pleroma.Web.MastodonAPI do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<div class="scopes-input">
 | 
			
		||||
  <%= label @form, :scope, "Permissions" %>
 | 
			
		||||
 | 
			
		||||
  <div class="scopes">
 | 
			
		||||
    <%= for scope <- @available_scopes do %>
 | 
			
		||||
      <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
 | 
			
		||||
      <div class="scope">
 | 
			
		||||
        <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %>
 | 
			
		||||
        <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
 | 
			
		||||
      </div>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										13
									
								
								lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<h2>Sign in with external provider</h2>
 | 
			
		||||
 | 
			
		||||
<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
 | 
			
		||||
  <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
 | 
			
		||||
 | 
			
		||||
  <%= hidden_input f, :client_id, value: @client_id %>
 | 
			
		||||
  <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
 | 
			
		||||
  <%= hidden_input f, :state, value: @state %>
 | 
			
		||||
 | 
			
		||||
    <%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
 | 
			
		||||
      <%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
<% end %>
 | 
			
		||||
							
								
								
									
										43
									
								
								lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
<%= if get_flash(@conn, :info) do %>
 | 
			
		||||
  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
 | 
			
		||||
<% end %>
 | 
			
		||||
<%= if get_flash(@conn, :error) do %>
 | 
			
		||||
  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 | 
			
		||||
<% end %>
 | 
			
		||||
 | 
			
		||||
<h2>Registration Details</h2>
 | 
			
		||||
 | 
			
		||||
<p>If you'd like to register a new account, please provide the details below.</p>
 | 
			
		||||
 | 
			
		||||
<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
 | 
			
		||||
 | 
			
		||||
<div class="input">
 | 
			
		||||
  <%= label f, :nickname, "Nickname" %>
 | 
			
		||||
  <%= text_input f, :nickname, value: @nickname %>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="input">
 | 
			
		||||
  <%= label f, :email, "Email" %>
 | 
			
		||||
  <%= text_input f, :email, value: @email %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<%= submit "Proceed as new user", name: "op", value: "register" %>
 | 
			
		||||
 | 
			
		||||
<p>Alternatively, sign in to connect to existing account.</p>
 | 
			
		||||
 | 
			
		||||
<div class="input">
 | 
			
		||||
  <%= label f, :auth_name, "Name or email" %>
 | 
			
		||||
  <%= text_input f, :auth_name %>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="input">
 | 
			
		||||
  <%= label f, :password, "Password" %>
 | 
			
		||||
  <%= password_input f, :password %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<%= submit "Proceed as existing user", name: "op", value: "connect" %>
 | 
			
		||||
 | 
			
		||||
<%= hidden_input f, :client_id, value: @client_id %>
 | 
			
		||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
 | 
			
		||||
<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
 | 
			
		||||
<%= hidden_input f, :state, value: @state %>
 | 
			
		||||
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,9 @@
 | 
			
		|||
<%= if get_flash(@conn, :error) do %>
 | 
			
		||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 | 
			
		||||
<% end %>
 | 
			
		||||
 | 
			
		||||
<h2>OAuth Authorization</h2>
 | 
			
		||||
 | 
			
		||||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 | 
			
		||||
<div class="input">
 | 
			
		||||
  <%= label f, :name, "Name or email" %>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,22 +16,16 @@
 | 
			
		|||
  <%= label f, :password, "Password" %>
 | 
			
		||||
  <%= password_input f, :password %>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="scopes-input">
 | 
			
		||||
<%= label f, :scope, "Permissions" %>
 | 
			
		||||
  <div class="scopes">
 | 
			
		||||
    <%= for scope <- @available_scopes do %>
 | 
			
		||||
      <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
 | 
			
		||||
      <div class="scope">
 | 
			
		||||
        <%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
 | 
			
		||||
        <%= label f, :"scope_#{scope}", String.capitalize(scope) %>
 | 
			
		||||
      </div>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f, scope_param: "authorization[scope][]"}) %>
 | 
			
		||||
 | 
			
		||||
<%= hidden_input f, :client_id, value: @client_id %>
 | 
			
		||||
<%= hidden_input f, :response_type, value: @response_type %>
 | 
			
		||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
 | 
			
		||||
<%= hidden_input f, :state, value: @state%>
 | 
			
		||||
<%= hidden_input f, :state, value: @state %>
 | 
			
		||||
<%= submit "Authorize" %>
 | 
			
		||||
<% end %>
 | 
			
		||||
 | 
			
		||||
<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
 | 
			
		||||
  <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -283,7 +283,13 @@ def version(conn, _params) do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def emoji(conn, _params) do
 | 
			
		||||
    json(conn, Enum.into(Emoji.get_all(), %{}))
 | 
			
		||||
    emoji =
 | 
			
		||||
      Emoji.get_all()
 | 
			
		||||
      |> Enum.map(fn {short_code, path, tags} ->
 | 
			
		||||
        %{short_code => %{image_url: path, tags: String.split(tags, ",")}}
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    json(conn, emoji)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								mix.exs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								mix.exs
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -54,6 +54,12 @@ defp elixirc_paths(_), do: ["lib"]
 | 
			
		|||
  #
 | 
			
		||||
  # Type `mix help deps` for examples and options.
 | 
			
		||||
  defp deps do
 | 
			
		||||
    oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
 | 
			
		||||
 | 
			
		||||
    oauth_deps =
 | 
			
		||||
      for s <- oauth_strategies,
 | 
			
		||||
          do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
 | 
			
		||||
 | 
			
		||||
    [
 | 
			
		||||
      {:phoenix, "~> 1.4.1"},
 | 
			
		||||
      {:plug_cowboy, "~> 2.0"},
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +77,7 @@ defp deps do
 | 
			
		|||
      {:calendar, "~> 0.17.4"},
 | 
			
		||||
      {:cachex, "~> 3.0.2"},
 | 
			
		||||
      {:httpoison, "~> 1.2.0"},
 | 
			
		||||
      {:poison, "~> 3.0", override: true},
 | 
			
		||||
      {:tesla, "~> 1.2"},
 | 
			
		||||
      {:jason, "~> 1.0"},
 | 
			
		||||
      {:mogrify, "~> 0.6.1"},
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +98,7 @@ defp deps do
 | 
			
		|||
      {:floki, "~> 0.20.0"},
 | 
			
		||||
      {:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
 | 
			
		||||
      {:timex, "~> 3.5"},
 | 
			
		||||
      {:ueberauth, "~> 0.4"},
 | 
			
		||||
      {:auto_linker,
 | 
			
		||||
       git: "https://git.pleroma.social/pleroma/auto_linker.git",
 | 
			
		||||
       ref: "479dd343f4e563ff91215c8275f3b5c67e032850"},
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +111,7 @@ defp deps do
 | 
			
		|||
      {:prometheus_process_collector, "~> 1.4"},
 | 
			
		||||
      {:recon, github: "ferd/recon", tag: "2.4.0"},
 | 
			
		||||
      {:quack, "~> 0.1.1"}
 | 
			
		||||
    ]
 | 
			
		||||
    ] ++ oauth_deps
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Aliases are shortcuts or tasks specific to the current project.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								mix.lock
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								mix.lock
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -5,7 +5,7 @@
 | 
			
		|||
  "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"},
 | 
			
		||||
  "calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
 | 
			
		||||
  "comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
 | 
			
		||||
  "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@
 | 
			
		|||
  "floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
 | 
			
		||||
  "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
 | 
			
		||||
  "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +40,7 @@
 | 
			
		|||
  "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
 | 
			
		||||
  "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +75,7 @@
 | 
			
		|||
  "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
  "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
 | 
			
		||||
  "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
 | 
			
		||||
  "web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								priv/repo/migrations/20190315101315_create_registrations.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								priv/repo/migrations/20190315101315_create_registrations.exs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
defmodule Pleroma.Repo.Migrations.CreateRegistrations do
 | 
			
		||||
  use Ecto.Migration
 | 
			
		||||
 | 
			
		||||
  def change do
 | 
			
		||||
    create table(:registrations, primary_key: false) do
 | 
			
		||||
      add :id, :uuid, primary_key: true
 | 
			
		||||
      add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
 | 
			
		||||
      add :provider, :string
 | 
			
		||||
      add :uid, :string
 | 
			
		||||
      add :info, :map, default: %{}
 | 
			
		||||
 | 
			
		||||
      timestamps()
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    create unique_index(:registrations, [:provider, :uid])
 | 
			
		||||
    create unique_index(:registrations, [:user_id, :provider, :uid])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										106
									
								
								test/emoji_test.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								test/emoji_test.exs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
defmodule Pleroma.EmojiTest do
 | 
			
		||||
  use ExUnit.Case, async: true
 | 
			
		||||
  alias Pleroma.Emoji
 | 
			
		||||
 | 
			
		||||
  describe "get_all/0" do
 | 
			
		||||
    setup do
 | 
			
		||||
      emoji_list = Emoji.get_all()
 | 
			
		||||
      {:ok, emoji_list: emoji_list}
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "first emoji", %{emoji_list: emoji_list} do
 | 
			
		||||
      [emoji | _others] = emoji_list
 | 
			
		||||
      {code, path, tags} = emoji
 | 
			
		||||
 | 
			
		||||
      assert tuple_size(emoji) == 3
 | 
			
		||||
      assert is_binary(code)
 | 
			
		||||
      assert is_binary(path)
 | 
			
		||||
      assert is_binary(tags)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "random emoji", %{emoji_list: emoji_list} do
 | 
			
		||||
      emoji = Enum.random(emoji_list)
 | 
			
		||||
      {code, path, tags} = emoji
 | 
			
		||||
 | 
			
		||||
      assert tuple_size(emoji) == 3
 | 
			
		||||
      assert is_binary(code)
 | 
			
		||||
      assert is_binary(path)
 | 
			
		||||
      assert is_binary(tags)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "match_extra/2" do
 | 
			
		||||
    setup do
 | 
			
		||||
      groups = [
 | 
			
		||||
        "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"],
 | 
			
		||||
        "wildcard folder": "/emoji/custom/*/file.png",
 | 
			
		||||
        "wildcard files": "/emoji/custom/folder/*.png",
 | 
			
		||||
        "special file": "/emoji/custom/special.png"
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      {:ok, groups: groups}
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config for list of files", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/first_file.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "list of files"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config with wildcard folder", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/some_folder/file.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "wildcard folder"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config with wildcard folder and subfolders", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "wildcard folder"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config with wildcard files", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/folder/some_file.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "wildcard files"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config with wildcard files and subfolders", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "wildcard files"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "config for special file", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/custom/special.png")
 | 
			
		||||
        |> to_string()
 | 
			
		||||
 | 
			
		||||
      assert group == "special file"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "no mathing returns nil", %{groups: groups} do
 | 
			
		||||
      group =
 | 
			
		||||
        groups
 | 
			
		||||
        |> Emoji.match_extra("/emoji/some_undefined.png")
 | 
			
		||||
 | 
			
		||||
      refute group
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +271,9 @@ test "it does not add XSS emoji" do
 | 
			
		|||
  test "it returns the emoji used in the text" do
 | 
			
		||||
    text = "I love :moominmamma:"
 | 
			
		||||
 | 
			
		||||
    assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}]
 | 
			
		||||
    assert Formatter.get_emoji(text) == [
 | 
			
		||||
             {"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"}
 | 
			
		||||
           ]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it returns a nice empty result when no emojis are present" do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										59
									
								
								test/registration_test.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								test/registration_test.exs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# Pleroma: A lightweight social networking server
 | 
			
		||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 | 
			
		||||
# SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 | 
			
		||||
defmodule Pleroma.RegistrationTest do
 | 
			
		||||
  use Pleroma.DataCase
 | 
			
		||||
 | 
			
		||||
  import Pleroma.Factory
 | 
			
		||||
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
 | 
			
		||||
  describe "generic changeset" do
 | 
			
		||||
    test "requires :provider, :uid" do
 | 
			
		||||
      registration = build(:registration, provider: nil, uid: nil)
 | 
			
		||||
 | 
			
		||||
      cs = Registration.changeset(registration, %{})
 | 
			
		||||
      refute cs.valid?
 | 
			
		||||
 | 
			
		||||
      assert [
 | 
			
		||||
               provider: {"can't be blank", [validation: :required]},
 | 
			
		||||
               uid: {"can't be blank", [validation: :required]}
 | 
			
		||||
             ] == cs.errors
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "ensures uniqueness of [:provider, :uid]" do
 | 
			
		||||
      registration = insert(:registration)
 | 
			
		||||
      registration2 = build(:registration, provider: registration.provider, uid: registration.uid)
 | 
			
		||||
 | 
			
		||||
      cs = Registration.changeset(registration2, %{})
 | 
			
		||||
      assert cs.valid?
 | 
			
		||||
 | 
			
		||||
      assert {:error,
 | 
			
		||||
              %Ecto.Changeset{
 | 
			
		||||
                errors: [
 | 
			
		||||
                  uid:
 | 
			
		||||
                    {"has already been taken",
 | 
			
		||||
                     [constraint: :unique, constraint_name: "registrations_provider_uid_index"]}
 | 
			
		||||
                ]
 | 
			
		||||
              }} = Repo.insert(cs)
 | 
			
		||||
 | 
			
		||||
      # Note: multiple :uid values per [:user_id, :provider] are intentionally allowed
 | 
			
		||||
      cs2 = Registration.changeset(registration2, %{uid: "available.uid"})
 | 
			
		||||
      assert cs2.valid?
 | 
			
		||||
      assert {:ok, _} = Repo.insert(cs2)
 | 
			
		||||
 | 
			
		||||
      cs3 = Registration.changeset(registration2, %{provider: "provider2"})
 | 
			
		||||
      assert cs3.valid?
 | 
			
		||||
      assert {:ok, _} = Repo.insert(cs3)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "allows `nil` :user_id (user-unbound registration)" do
 | 
			
		||||
      registration = build(:registration, user_id: nil)
 | 
			
		||||
      cs = Registration.changeset(registration, %{})
 | 
			
		||||
      assert cs.valid?
 | 
			
		||||
      assert {:ok, _} = Repo.insert(cs)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -275,4 +275,20 @@ def scheduled_activity_factory do
 | 
			
		|||
      params: build(:note) |> Map.from_struct() |> Map.get(:data)
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def registration_factory do
 | 
			
		||||
    user = insert(:user)
 | 
			
		||||
 | 
			
		||||
    %Pleroma.Registration{
 | 
			
		||||
      user: user,
 | 
			
		||||
      provider: "twitter",
 | 
			
		||||
      uid: "171799000",
 | 
			
		||||
      info: %{
 | 
			
		||||
        "name" => "John Doe",
 | 
			
		||||
        "email" => "john@doe.com",
 | 
			
		||||
        "nickname" => "johndoe",
 | 
			
		||||
        "description" => "My bio"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1028,9 +1028,6 @@ test "it upgrades a user to activitypub" do
 | 
			
		|||
      assert user.info.note_count == 1
 | 
			
		||||
      assert user.follower_address == "https://niu.moe/users/rye/followers"
 | 
			
		||||
 | 
			
		||||
      # Wait for the background task
 | 
			
		||||
      :timer.sleep(1000)
 | 
			
		||||
 | 
			
		||||
      user = User.get_by_id(user.id)
 | 
			
		||||
      assert user.info.note_count == 1
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -193,4 +193,16 @@ test "returns the what was collected if there are less pages than specified" do
 | 
			
		|||
      assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "make_json_ld_header/0" do
 | 
			
		||||
    assert Utils.make_json_ld_header() == %{
 | 
			
		||||
             "@context" => [
 | 
			
		||||
               "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
               "http://localhost:4001/schemas/litepub-0.1.jsonld",
 | 
			
		||||
               %{
 | 
			
		||||
                 "@language" => "und"
 | 
			
		||||
               }
 | 
			
		||||
             ]
 | 
			
		||||
           }
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,14 +80,13 @@ test "allows to force-follow another user" do
 | 
			
		|||
      user = insert(:user)
 | 
			
		||||
      follower = insert(:user)
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        build_conn()
 | 
			
		||||
        |> assign(:user, admin)
 | 
			
		||||
        |> put_req_header("accept", "application/json")
 | 
			
		||||
        |> post("/api/pleroma/admin/user/follow", %{
 | 
			
		||||
          "follower" => follower.nickname,
 | 
			
		||||
          "followed" => user.nickname
 | 
			
		||||
        })
 | 
			
		||||
      build_conn()
 | 
			
		||||
      |> assign(:user, admin)
 | 
			
		||||
      |> put_req_header("accept", "application/json")
 | 
			
		||||
      |> post("/api/pleroma/admin/user/follow", %{
 | 
			
		||||
        "follower" => follower.nickname,
 | 
			
		||||
        "followed" => user.nickname
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      user = User.get_by_id(user.id)
 | 
			
		||||
      follower = User.get_by_id(follower.id)
 | 
			
		||||
| 
						 | 
				
			
			@ -104,14 +103,13 @@ test "allows to force-unfollow another user" do
 | 
			
		|||
 | 
			
		||||
      User.follow(follower, user)
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        build_conn()
 | 
			
		||||
        |> assign(:user, admin)
 | 
			
		||||
        |> put_req_header("accept", "application/json")
 | 
			
		||||
        |> post("/api/pleroma/admin/user/unfollow", %{
 | 
			
		||||
          "follower" => follower.nickname,
 | 
			
		||||
          "followed" => user.nickname
 | 
			
		||||
        })
 | 
			
		||||
      build_conn()
 | 
			
		||||
      |> assign(:user, admin)
 | 
			
		||||
      |> put_req_header("accept", "application/json")
 | 
			
		||||
      |> post("/api/pleroma/admin/user/unfollow", %{
 | 
			
		||||
        "follower" => follower.nickname,
 | 
			
		||||
        "followed" => user.nickname
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      user = User.get_by_id(user.id)
 | 
			
		||||
      follower = User.get_by_id(follower.id)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2342,6 +2342,22 @@ test "accounts fetches correct account for nicknames beginning with numbers", %{
 | 
			
		|||
    assert acc_two == acc_three
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "custom emoji" do
 | 
			
		||||
    test "with tags", %{conn: conn} do
 | 
			
		||||
      [emoji | _body] =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/custom_emojis")
 | 
			
		||||
        |> json_response(200)
 | 
			
		||||
 | 
			
		||||
      assert Map.has_key?(emoji, "shortcode")
 | 
			
		||||
      assert Map.has_key?(emoji, "static_url")
 | 
			
		||||
      assert Map.has_key?(emoji, "tags")
 | 
			
		||||
      assert is_list(emoji["tags"])
 | 
			
		||||
      assert Map.has_key?(emoji, "url")
 | 
			
		||||
      assert Map.has_key?(emoji, "visible_in_picker")
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "index/2 redirections" do
 | 
			
		||||
    setup %{conn: conn} do
 | 
			
		||||
      session_opts = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,24 +5,330 @@
 | 
			
		|||
defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 | 
			
		||||
  use Pleroma.Web.ConnCase
 | 
			
		||||
  import Pleroma.Factory
 | 
			
		||||
  import Mock
 | 
			
		||||
 | 
			
		||||
  alias Pleroma.Registration
 | 
			
		||||
  alias Pleroma.Repo
 | 
			
		||||
  alias Pleroma.Web.OAuth.Authorization
 | 
			
		||||
  alias Pleroma.Web.OAuth.Token
 | 
			
		||||
 | 
			
		||||
  @session_opts [
 | 
			
		||||
    store: :cookie,
 | 
			
		||||
    key: "_test",
 | 
			
		||||
    signing_salt: "cooldude"
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  describe "in OAuth consumer mode, " do
 | 
			
		||||
    setup do
 | 
			
		||||
      oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies]
 | 
			
		||||
      oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path)
 | 
			
		||||
      Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook))
 | 
			
		||||
 | 
			
		||||
      on_exit(fn ->
 | 
			
		||||
        Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies)
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
      [
 | 
			
		||||
        app: insert(:oauth_app),
 | 
			
		||||
        conn:
 | 
			
		||||
          build_conn()
 | 
			
		||||
          |> Plug.Session.call(Plug.Session.init(@session_opts))
 | 
			
		||||
          |> fetch_session()
 | 
			
		||||
      ]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
 | 
			
		||||
      app: app,
 | 
			
		||||
      conn: conn
 | 
			
		||||
    } do
 | 
			
		||||
      conn =
 | 
			
		||||
        get(
 | 
			
		||||
          conn,
 | 
			
		||||
          "/oauth/authorize",
 | 
			
		||||
          %{
 | 
			
		||||
            "response_type" => "code",
 | 
			
		||||
            "client_id" => app.client_id,
 | 
			
		||||
            "redirect_uri" => app.redirect_uris,
 | 
			
		||||
            "scope" => "read"
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 200)
 | 
			
		||||
      assert response =~ "Sign in with Twitter"
 | 
			
		||||
      assert response =~ o_auth_path(conn, :prepare_request)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
 | 
			
		||||
      app: app,
 | 
			
		||||
      conn: conn
 | 
			
		||||
    } do
 | 
			
		||||
      conn =
 | 
			
		||||
        get(
 | 
			
		||||
          conn,
 | 
			
		||||
          "/oauth/prepare_request",
 | 
			
		||||
          %{
 | 
			
		||||
            "provider" => "twitter",
 | 
			
		||||
            "scope" => "read follow",
 | 
			
		||||
            "client_id" => app.client_id,
 | 
			
		||||
            "redirect_uri" => app.redirect_uris,
 | 
			
		||||
            "state" => "a_state"
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 302)
 | 
			
		||||
 | 
			
		||||
      redirect_query = URI.parse(redirected_to(conn)).query
 | 
			
		||||
      assert %{"state" => state_param} = URI.decode_query(redirect_query)
 | 
			
		||||
      assert {:ok, state_components} = Poison.decode(state_param)
 | 
			
		||||
 | 
			
		||||
      expected_client_id = app.client_id
 | 
			
		||||
      expected_redirect_uri = app.redirect_uris
 | 
			
		||||
 | 
			
		||||
      assert %{
 | 
			
		||||
               "scope" => "read follow",
 | 
			
		||||
               "client_id" => ^expected_client_id,
 | 
			
		||||
               "redirect_uri" => ^expected_redirect_uri,
 | 
			
		||||
               "state" => "a_state"
 | 
			
		||||
             } = state_components
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
 | 
			
		||||
         %{app: app, conn: conn} do
 | 
			
		||||
      registration = insert(:registration)
 | 
			
		||||
 | 
			
		||||
      state_params = %{
 | 
			
		||||
        "scope" => Enum.join(app.scopes, " "),
 | 
			
		||||
        "client_id" => app.client_id,
 | 
			
		||||
        "redirect_uri" => app.redirect_uris,
 | 
			
		||||
        "state" => ""
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      with_mock Pleroma.Web.Auth.Authenticator,
 | 
			
		||||
        get_registration: fn _, _ -> {:ok, registration} end do
 | 
			
		||||
        conn =
 | 
			
		||||
          get(
 | 
			
		||||
            conn,
 | 
			
		||||
            "/oauth/twitter/callback",
 | 
			
		||||
            %{
 | 
			
		||||
              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
 | 
			
		||||
              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
 | 
			
		||||
              "provider" => "twitter",
 | 
			
		||||
              "state" => Poison.encode!(state_params)
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
        assert response = html_response(conn, 302)
 | 
			
		||||
        assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
 | 
			
		||||
         %{app: app, conn: conn} do
 | 
			
		||||
      registration = insert(:registration, user: nil)
 | 
			
		||||
 | 
			
		||||
      state_params = %{
 | 
			
		||||
        "scope" => "read write",
 | 
			
		||||
        "client_id" => app.client_id,
 | 
			
		||||
        "redirect_uri" => app.redirect_uris,
 | 
			
		||||
        "state" => "a_state"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      with_mock Pleroma.Web.Auth.Authenticator,
 | 
			
		||||
        get_registration: fn _, _ -> {:ok, registration} end do
 | 
			
		||||
        conn =
 | 
			
		||||
          get(
 | 
			
		||||
            conn,
 | 
			
		||||
            "/oauth/twitter/callback",
 | 
			
		||||
            %{
 | 
			
		||||
              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
 | 
			
		||||
              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
 | 
			
		||||
              "provider" => "twitter",
 | 
			
		||||
              "state" => Poison.encode!(state_params)
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
        assert response = html_response(conn, 200)
 | 
			
		||||
        assert response =~ ~r/name="op" type="submit" value="register"/
 | 
			
		||||
        assert response =~ ~r/name="op" type="submit" value="connect"/
 | 
			
		||||
        assert response =~ Registration.email(registration)
 | 
			
		||||
        assert response =~ Registration.nickname(registration)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
 | 
			
		||||
      app: app,
 | 
			
		||||
      conn: conn
 | 
			
		||||
    } do
 | 
			
		||||
      state_params = %{
 | 
			
		||||
        "scope" => Enum.join(app.scopes, " "),
 | 
			
		||||
        "client_id" => app.client_id,
 | 
			
		||||
        "redirect_uri" => app.redirect_uris,
 | 
			
		||||
        "state" => ""
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        conn
 | 
			
		||||
        |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
 | 
			
		||||
        |> get(
 | 
			
		||||
          "/oauth/twitter/callback",
 | 
			
		||||
          %{
 | 
			
		||||
            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
 | 
			
		||||
            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
 | 
			
		||||
            "provider" => "twitter",
 | 
			
		||||
            "state" => Poison.encode!(state_params)
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 302)
 | 
			
		||||
      assert redirected_to(conn) == app.redirect_uris
 | 
			
		||||
      assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "GET /oauth/registration_details renders registration details form", %{
 | 
			
		||||
      app: app,
 | 
			
		||||
      conn: conn
 | 
			
		||||
    } do
 | 
			
		||||
      conn =
 | 
			
		||||
        get(
 | 
			
		||||
          conn,
 | 
			
		||||
          "/oauth/registration_details",
 | 
			
		||||
          %{
 | 
			
		||||
            "scopes" => app.scopes,
 | 
			
		||||
            "client_id" => app.client_id,
 | 
			
		||||
            "redirect_uri" => app.redirect_uris,
 | 
			
		||||
            "state" => "a_state",
 | 
			
		||||
            "nickname" => nil,
 | 
			
		||||
            "email" => "john@doe.com"
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 200)
 | 
			
		||||
      assert response =~ ~r/name="op" type="submit" value="register"/
 | 
			
		||||
      assert response =~ ~r/name="op" type="submit" value="connect"/
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
 | 
			
		||||
         %{
 | 
			
		||||
           app: app,
 | 
			
		||||
           conn: conn
 | 
			
		||||
         } do
 | 
			
		||||
      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_session(:registration_id, registration.id)
 | 
			
		||||
        |> post(
 | 
			
		||||
          "/oauth/register",
 | 
			
		||||
          %{
 | 
			
		||||
            "op" => "register",
 | 
			
		||||
            "scopes" => app.scopes,
 | 
			
		||||
            "client_id" => app.client_id,
 | 
			
		||||
            "redirect_uri" => app.redirect_uris,
 | 
			
		||||
            "state" => "a_state",
 | 
			
		||||
            "nickname" => "availablenick",
 | 
			
		||||
            "email" => "available@email.com"
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 302)
 | 
			
		||||
      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with invalid params, POST /oauth/register?op=register renders registration_details page",
 | 
			
		||||
         %{
 | 
			
		||||
           app: app,
 | 
			
		||||
           conn: conn
 | 
			
		||||
         } do
 | 
			
		||||
      another_user = insert(:user)
 | 
			
		||||
      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
 | 
			
		||||
 | 
			
		||||
      params = %{
 | 
			
		||||
        "op" => "register",
 | 
			
		||||
        "scopes" => app.scopes,
 | 
			
		||||
        "client_id" => app.client_id,
 | 
			
		||||
        "redirect_uri" => app.redirect_uris,
 | 
			
		||||
        "state" => "a_state",
 | 
			
		||||
        "nickname" => "availablenickname",
 | 
			
		||||
        "email" => "available@email.com"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for {bad_param, bad_param_value} <-
 | 
			
		||||
            [{"nickname", another_user.nickname}, {"email", another_user.email}] do
 | 
			
		||||
        bad_params = Map.put(params, bad_param, bad_param_value)
 | 
			
		||||
 | 
			
		||||
        conn =
 | 
			
		||||
          conn
 | 
			
		||||
          |> put_session(:registration_id, registration.id)
 | 
			
		||||
          |> post("/oauth/register", bad_params)
 | 
			
		||||
 | 
			
		||||
        assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
 | 
			
		||||
        assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
 | 
			
		||||
         %{
 | 
			
		||||
           app: app,
 | 
			
		||||
           conn: conn
 | 
			
		||||
         } do
 | 
			
		||||
      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
 | 
			
		||||
      registration = insert(:registration, user: nil)
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_session(:registration_id, registration.id)
 | 
			
		||||
        |> post(
 | 
			
		||||
          "/oauth/register",
 | 
			
		||||
          %{
 | 
			
		||||
            "op" => "connect",
 | 
			
		||||
            "scopes" => app.scopes,
 | 
			
		||||
            "client_id" => app.client_id,
 | 
			
		||||
            "redirect_uri" => app.redirect_uris,
 | 
			
		||||
            "state" => "a_state",
 | 
			
		||||
            "auth_name" => user.nickname,
 | 
			
		||||
            "password" => "testpassword"
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      assert response = html_response(conn, 302)
 | 
			
		||||
      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
 | 
			
		||||
         %{
 | 
			
		||||
           app: app,
 | 
			
		||||
           conn: conn
 | 
			
		||||
         } do
 | 
			
		||||
      user = insert(:user)
 | 
			
		||||
      registration = insert(:registration, user: nil)
 | 
			
		||||
 | 
			
		||||
      params = %{
 | 
			
		||||
        "op" => "connect",
 | 
			
		||||
        "scopes" => app.scopes,
 | 
			
		||||
        "client_id" => app.client_id,
 | 
			
		||||
        "redirect_uri" => app.redirect_uris,
 | 
			
		||||
        "state" => "a_state",
 | 
			
		||||
        "auth_name" => user.nickname,
 | 
			
		||||
        "password" => "wrong password"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      conn =
 | 
			
		||||
        conn
 | 
			
		||||
        |> put_session(:registration_id, registration.id)
 | 
			
		||||
        |> post("/oauth/register", params)
 | 
			
		||||
 | 
			
		||||
      assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
 | 
			
		||||
      assert get_flash(conn, :error) == "Invalid Username/Password"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "GET /oauth/authorize" do
 | 
			
		||||
    setup do
 | 
			
		||||
      session_opts = [
 | 
			
		||||
        store: :cookie,
 | 
			
		||||
        key: "_test",
 | 
			
		||||
        signing_salt: "cooldude"
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      [
 | 
			
		||||
        app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
 | 
			
		||||
        conn:
 | 
			
		||||
          build_conn()
 | 
			
		||||
          |> Plug.Session.call(Plug.Session.init(session_opts))
 | 
			
		||||
          |> Plug.Session.call(Plug.Session.init(@session_opts))
 | 
			
		||||
          |> fetch_session()
 | 
			
		||||
      ]
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -170,6 +170,27 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "/api/pleroma/emoji" do
 | 
			
		||||
    test "returns json with custom emoji with tags", %{conn: conn} do
 | 
			
		||||
      [emoji | _body] =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/pleroma/emoji")
 | 
			
		||||
        |> json_response(200)
 | 
			
		||||
 | 
			
		||||
      [key] = Map.keys(emoji)
 | 
			
		||||
 | 
			
		||||
      %{
 | 
			
		||||
        ^key => %{
 | 
			
		||||
          "image_url" => url,
 | 
			
		||||
          "tags" => tags
 | 
			
		||||
        }
 | 
			
		||||
      } = emoji
 | 
			
		||||
 | 
			
		||||
      assert is_binary(url)
 | 
			
		||||
      assert is_list(tags)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "GET /ostatus_subscribe?acct=...." do
 | 
			
		||||
    test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
 | 
			
		||||
      conn =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue