discourse/app/serializers/user_serializer.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

334 lines
8.1 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
class UserSerializer < UserCardSerializer
include UserTagNotificationsMixin
include UserSidebarMixin
2013-02-07 23:45:24 +08:00
attributes :bio_raw,
2013-02-07 23:45:24 +08:00
:bio_cooked,
:can_edit,
:can_edit_username,
:can_edit_email,
:can_edit_name,
2014-06-02 10:59:54 +08:00
:uploaded_avatar_id,
2014-07-28 01:12:36 +08:00
:has_title_badges,
2015-09-14 15:51:17 +08:00
:pending_count,
2016-09-16 04:15:08 +08:00
:profile_view_count,
2018-06-28 16:12:32 +08:00
:second_factor_enabled,
:second_factor_backup_enabled,
:second_factor_remaining_backup_codes,
:associated_accounts,
:profile_background_upload_url,
:can_upload_profile_header,
:can_upload_user_card_background
2013-02-06 03:16:51 +08:00
has_one :invited_by, embed: :object, serializer: BasicUserSerializer
has_many :groups, embed: :object, serializer: BasicGroupSerializer
has_many :group_users, embed: :object, serializer: BasicGroupUserSerializer
has_one :user_option, embed: :object, serializer: UserOptionSerializer
def include_user_option?
can_edit
end
2013-02-06 03:16:51 +08:00
staff_attributes :post_count, :can_be_deleted, :can_delete_all_posts
private_attributes :locale,
:muted_category_ids,
:regular_category_ids,
:watched_tags,
:watching_first_post_tags,
:tracked_tags,
:muted_tags,
:tracked_category_ids,
:watched_category_ids,
:watched_first_post_category_ids,
2015-09-11 18:56:34 +08:00
:system_avatar_upload_id,
:system_avatar_template,
:gravatar_avatar_upload_id,
2015-09-11 18:56:34 +08:00
:gravatar_avatar_template,
:custom_avatar_upload_id,
2015-09-11 18:56:34 +08:00
:custom_avatar_template,
:has_title_badges,
:muted_usernames,
:ignored_usernames,
:allowed_pm_usernames,
:mailing_list_posts_per_day,
:can_change_bio,
:can_change_location,
:can_change_website,
:can_change_tracking_preferences,
:user_api_keys,
DEV: Add routes and controller actions for passkeys (2/3) (#23587) This is part 2 (of 3) for passkeys support. This adds a hidden site setting plus routes and controller actions. 1. registering passkeys Passkeys are registered in a two-step process. First, `create_passkey` returns details for the browser to create a passkey. This includes - a challenge - the relying party ID and Origin - the user's secure identifier - the supported algorithms - the user's existing passkeys (if any) Then the browser creates a key with this information, and submits it to the server via `register_passkey`. 2. authenticating passkeys A similar process happens here as well. First, a challenge is created and sent to the browser. Then the browser makes a public key credential and submits it to the server via `passkey_auth_perform`. 3. renaming/deleting passkeys These routes allow changing the name of a key and deleting it. 4. checking if session is trusted for sensitive actions Since a passkey is a password replacement, we want to make sure to confirm the user's identity before allowing adding/deleting passkeys. The u/trusted-session GET route returns success if user has confirmed their session (and failed if user hasn't). In the frontend (in the next PR), we're using these routes to show the password confirmation screen. The `/u/confirm-session` route allows the user to confirm their session with a password. The latter route's functionality already existed in core, under the 2FA flow, but it has been abstracted into its own here so it can be used independently. Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2023-10-12 02:36:54 +08:00
:user_passkeys,
:user_auth_tokens,
:user_notification_schedule,
:use_logo_small_as_avatar,
:sidebar_tags,
:sidebar_category_ids,
:display_sidebar_tags
untrusted_attributes :bio_raw, :bio_cooked, :profile_background_upload_url
###
### ATTRIBUTES
###
#
def user_notification_schedule
object.user_notification_schedule || UserNotificationSchedule::DEFAULT
end
def mailing_list_posts_per_day
val = Post.estimate_posts_per_day
[val, SiteSetting.max_emails_per_day_per_user].min
end
def groups
object.groups.order(:id).visible_groups(scope.user).members_visible_groups(scope.user)
end
def group_users
object.group_users.order(:group_id)
end
def include_group_users?
user_is_current_user || scope.is_admin?
end
def include_associated_accounts?
user_is_current_user
end
def include_second_factor_enabled?
user_is_current_user || scope.is_admin?
end
def second_factor_enabled
object.totp_enabled? || object.security_keys_enabled?
end
2018-06-28 16:12:32 +08:00
def include_second_factor_backup_enabled?
user_is_current_user
2018-06-28 16:12:32 +08:00
end
def second_factor_backup_enabled
object.backup_codes_enabled?
end
def include_second_factor_remaining_backup_codes?
user_is_current_user && object.backup_codes_enabled?
end
def second_factor_remaining_backup_codes
object.remaining_backup_codes
end
def can_change_bio
FEATURE: Rename 'Discourse SSO' to DiscourseConnect (#11978) The 'Discourse SSO' protocol is being rebranded to DiscourseConnect. This should help to reduce confusion when 'SSO' is used in the generic sense. This commit aims to: - Rename `sso_` site settings. DiscourseConnect specific ones are prefixed `discourse_connect_`. Generic settings are prefixed `auth_` - Add (server-side-only) backwards compatibility for the old setting names, with deprecation notices - Copy `site_settings` database records to the new names - Rename relevant translation keys - Update relevant translations This commit does **not** aim to: - Rename any Ruby classes or methods. This might be done in a future commit - Change any URLs. This would break existing integrations - Make any changes to the protocol. This would break existing integrations - Change any functionality. Further normalization across DiscourseConnect and other auth methods will be done separately The risks are: - There is no backwards compatibility for site settings on the client-side. Accessing auth-related site settings in Javascript is fairly rare, and an error on the client side would not be security-critical. - If a plugin is monkey-patching parts of the auth process, changes to locale keys could cause broken error messages. This should also be unlikely. The old site setting names remain functional, so security-related overrides will remain working. A follow-up commit will be made with a post-deploy migration to delete the old `site_settings` rows.
2021-02-08 18:04:33 +08:00
!(SiteSetting.enable_discourse_connect && SiteSetting.discourse_connect_overrides_bio)
end
def can_change_location
FEATURE: Rename 'Discourse SSO' to DiscourseConnect (#11978) The 'Discourse SSO' protocol is being rebranded to DiscourseConnect. This should help to reduce confusion when 'SSO' is used in the generic sense. This commit aims to: - Rename `sso_` site settings. DiscourseConnect specific ones are prefixed `discourse_connect_`. Generic settings are prefixed `auth_` - Add (server-side-only) backwards compatibility for the old setting names, with deprecation notices - Copy `site_settings` database records to the new names - Rename relevant translation keys - Update relevant translations This commit does **not** aim to: - Rename any Ruby classes or methods. This might be done in a future commit - Change any URLs. This would break existing integrations - Make any changes to the protocol. This would break existing integrations - Change any functionality. Further normalization across DiscourseConnect and other auth methods will be done separately The risks are: - There is no backwards compatibility for site settings on the client-side. Accessing auth-related site settings in Javascript is fairly rare, and an error on the client side would not be security-critical. - If a plugin is monkey-patching parts of the auth process, changes to locale keys could cause broken error messages. This should also be unlikely. The old site setting names remain functional, so security-related overrides will remain working. A follow-up commit will be made with a post-deploy migration to delete the old `site_settings` rows.
2021-02-08 18:04:33 +08:00
!(SiteSetting.enable_discourse_connect && SiteSetting.discourse_connect_overrides_location)
end
def can_change_website
FEATURE: Rename 'Discourse SSO' to DiscourseConnect (#11978) The 'Discourse SSO' protocol is being rebranded to DiscourseConnect. This should help to reduce confusion when 'SSO' is used in the generic sense. This commit aims to: - Rename `sso_` site settings. DiscourseConnect specific ones are prefixed `discourse_connect_`. Generic settings are prefixed `auth_` - Add (server-side-only) backwards compatibility for the old setting names, with deprecation notices - Copy `site_settings` database records to the new names - Rename relevant translation keys - Update relevant translations This commit does **not** aim to: - Rename any Ruby classes or methods. This might be done in a future commit - Change any URLs. This would break existing integrations - Make any changes to the protocol. This would break existing integrations - Change any functionality. Further normalization across DiscourseConnect and other auth methods will be done separately The risks are: - There is no backwards compatibility for site settings on the client-side. Accessing auth-related site settings in Javascript is fairly rare, and an error on the client side would not be security-critical. - If a plugin is monkey-patching parts of the auth process, changes to locale keys could cause broken error messages. This should also be unlikely. The old site setting names remain functional, so security-related overrides will remain working. A follow-up commit will be made with a post-deploy migration to delete the old `site_settings` rows.
2021-02-08 18:04:33 +08:00
!(SiteSetting.enable_discourse_connect && SiteSetting.discourse_connect_overrides_website)
end
def can_change_tracking_preferences
scope.can_change_tracking_preferences?(object)
end
def user_api_keys
keys =
object
.user_api_keys
.where(revoked_at: nil)
.map do |k|
{
id: k.id,
application_name: k.application_name,
scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s.name}") },
created_at: k.created_at,
last_used_at: k.last_used_at,
}
end
keys.sort! { |a, b| a[:last_used_at].to_time <=> b[:last_used_at].to_time }
keys.length > 0 ? keys : nil
end
def user_auth_tokens
ActiveModel::ArraySerializer.new(
object.user_auth_tokens,
each_serializer: UserAuthTokenSerializer,
scope: scope,
)
end
DEV: Add routes and controller actions for passkeys (2/3) (#23587) This is part 2 (of 3) for passkeys support. This adds a hidden site setting plus routes and controller actions. 1. registering passkeys Passkeys are registered in a two-step process. First, `create_passkey` returns details for the browser to create a passkey. This includes - a challenge - the relying party ID and Origin - the user's secure identifier - the supported algorithms - the user's existing passkeys (if any) Then the browser creates a key with this information, and submits it to the server via `register_passkey`. 2. authenticating passkeys A similar process happens here as well. First, a challenge is created and sent to the browser. Then the browser makes a public key credential and submits it to the server via `passkey_auth_perform`. 3. renaming/deleting passkeys These routes allow changing the name of a key and deleting it. 4. checking if session is trusted for sensitive actions Since a passkey is a password replacement, we want to make sure to confirm the user's identity before allowing adding/deleting passkeys. The u/trusted-session GET route returns success if user has confirmed their session (and failed if user hasn't). In the frontend (in the next PR), we're using these routes to show the password confirmation screen. The `/u/confirm-session` route allows the user to confirm their session with a password. The latter route's functionality already existed in core, under the 2FA flow, but it has been abstracted into its own here so it can be used independently. Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2023-10-12 02:36:54 +08:00
def user_passkeys
UserSecurityKey
.where(user_id: object.id, factor_type: UserSecurityKey.factor_types[:first_factor])
.map do |usk|
{ id: usk.id, name: usk.name, last_used: usk.last_used, created_at: usk.created_at }
end
end
def include_user_passkeys?
SiteSetting.experimental_passkeys?
end
def bio_raw
object.user_profile.bio_raw
end
def bio_cooked
object.user_profile.bio_processed
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
2013-02-06 03:16:51 +08:00
def can_edit
scope.can_edit?(object)
end
def can_edit_username
scope.can_edit_username?(object)
end
def can_edit_email
scope.can_edit_email?(object)
end
def can_edit_name
scope.can_edit_name?(object)
end
def can_upload_profile_header
scope.can_upload_profile_header?(object)
end
def can_upload_user_card_background
scope.can_upload_user_card_background?(object)
end
###
### STAFF ATTRIBUTES
###
def post_count
object.user_stat.try(:post_count)
end
def can_be_deleted
scope.can_delete_user?(object)
end
def can_delete_all_posts
scope.can_delete_all_posts?(object)
end
###
### PRIVATE ATTRIBUTES
###
def muted_category_ids
categories_with_notification_level(:muted)
end
def regular_category_ids
categories_with_notification_level(:regular)
end
def tracked_category_ids
categories_with_notification_level(:tracking)
end
def watched_category_ids
categories_with_notification_level(:watching)
end
def watched_first_post_category_ids
categories_with_notification_level(:watching_first_post)
end
def muted_usernames
MutedUser.where(user_id: object.id).joins(:muted_user).pluck(:username)
end
def ignored_usernames
IgnoredUser.where(user_id: object.id).joins(:ignored_user).pluck(:username)
end
def allowed_pm_usernames
AllowedPmUser.where(user_id: object.id).joins(:allowed_pm_user).pluck(:username)
end
2015-09-11 18:56:34 +08:00
def system_avatar_upload_id
# should be left blank
end
def system_avatar_template
User.system_avatar_template(object.username)
end
2018-05-21 11:25:01 +08:00
def include_gravatar_avatar_upload_id?
object.user_avatar&.gravatar_upload_id
end
def gravatar_avatar_upload_id
2018-05-21 11:25:01 +08:00
object.user_avatar.gravatar_upload_id
end
def include_gravatar_avatar_template?
include_gravatar_avatar_upload_id?
end
2015-09-11 18:56:34 +08:00
def gravatar_avatar_template
2018-05-21 11:25:01 +08:00
User.avatar_template(object.username, object.user_avatar.gravatar_upload_id)
end
def include_custom_avatar_upload_id?
object.user_avatar&.custom_upload_id
2015-09-11 18:56:34 +08:00
end
def custom_avatar_upload_id
2018-05-21 11:25:01 +08:00
object.user_avatar.custom_upload_id
end
def include_custom_avatar_template?
include_custom_avatar_upload_id?
end
2015-09-11 18:56:34 +08:00
def custom_avatar_template
2018-05-21 11:25:01 +08:00
User.avatar_template(object.username, object.user_avatar.custom_upload_id)
2015-09-11 18:56:34 +08:00
end
def has_title_badges
2017-08-24 06:54:51 +08:00
object.badges.where(allow_title: true).exists?
end
2015-04-22 02:36:46 +08:00
def pending_count
0
end
2015-09-14 15:51:17 +08:00
def profile_view_count
object.user_profile.views
2015-09-14 15:51:17 +08:00
end
def profile_background_upload_url
object.profile_background_upload&.url
end
def use_logo_small_as_avatar
object.is_system_user? && SiteSetting.logo_small &&
SiteSetting.use_site_small_logo_as_system_avatar
end
private
def custom_field_keys
fields = super
fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a if scope.can_edit?(object)
fields
end
2013-02-06 03:16:51 +08:00
end