diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 07cb1d41f8..ee9eefd458 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -107,6 +107,10 @@ jobs: PAM_ENABLED: true PAM_DEFAULT_SERVICE: pam_test PAM_CONTROLLED_SERVICE: pam_test_controlled + OIDC_ENABLED: true + OIDC_SCOPE: read + SAML_ENABLED: true + CAS_ENABLED: true BUNDLE_WITH: 'pam_authentication test' CI_JOBS: ${{ matrix.ci_job }}/4 diff --git a/Gemfile.lock b/Gemfile.lock index bc4f3522bb..d308192e8b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,7 +103,7 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.786.0) + aws-partitions (1.791.0) aws-sdk-core (3.178.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) @@ -112,7 +112,7 @@ GEM aws-sdk-kms (1.71.0) aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.130.0) + aws-sdk-s3 (1.131.0) aws-sdk-core (~> 3, >= 3.177.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) @@ -144,7 +144,7 @@ GEM blurhash (0.1.7) bootsnap (1.16.0) msgpack (~> 1.2) - brakeman (6.0.0) + brakeman (6.0.1) browser (5.3.1) brpoplpush-redis_script (0.1.3) concurrent-ruby (~> 1.0, >= 1.0.5) @@ -569,7 +569,7 @@ GEM rake (13.0.6) rdf (3.2.11) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.6.0) + rdf-normalize (0.6.1) rdf (~> 3.2) redcarpet (3.6.0) redis (4.8.1) diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb index 9e0fb942aa..4723806b92 100644 --- a/app/controllers/auth/omniauth_callbacks_controller.rb +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -5,21 +5,13 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController def self.provides_callback_for(provider) define_method provider do + @provider = provider @user = User.find_for_oauth(request.env['omniauth.auth'], current_user) if @user.persisted? - LoginActivity.create( - user: @user, - success: true, - authentication_method: :omniauth, - provider: provider, - ip: request.remote_ip, - user_agent: request.user_agent - ) - + record_login_activity sign_in_and_redirect @user, event: :authentication - label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize) - set_flash_message(:notice, :success, kind: label) if is_navigational_format? + set_flash_message(:notice, :success, kind: label_for_provider) if is_navigational_format? else session["devise.#{provider}_data"] = request.env['omniauth.auth'] redirect_to new_user_registration_url @@ -38,4 +30,29 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController auth_setup_path(missing_email: '1') end end + + private + + def record_login_activity + LoginActivity.create( + user: @user, + success: true, + authentication_method: :omniauth, + provider: @provider, + ip: request.remote_ip, + user_agent: request.user_agent + ) + end + + def label_for_provider + provider_display_name || configured_provider_name + end + + def provider_display_name + Devise.omniauth_configs[@provider]&.strategy&.display_name.presence + end + + def configured_provider_name + I18n.t("auth.providers.#{@provider}", default: @provider.to_s.chomp('_oauth2').capitalize) + end end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index dc88933671..d59250b31c 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -113,7 +113,7 @@ class Auth::SessionsController < Devise::SessionsController end def home_paths(resource) - paths = [about_path] + paths = [about_path, '/explore'] paths << short_account_path(username: resource.account) if single_user_mode? && resource.is_a?(User) diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index e3c0065c95..0b633a5b40 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -12,7 +12,7 @@ import { debounce } from 'lodash'; import { Blurhash } from 'mastodon/components/blurhash'; -import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state'; +import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; import { IconButton } from './icon_button'; @@ -209,7 +209,6 @@ class MediaGallery extends PureComponent { static propTypes = { sensitive: PropTypes.bool, - standalone: PropTypes.bool, media: ImmutablePropTypes.list.isRequired, lang: PropTypes.string, size: PropTypes.object, @@ -223,10 +222,6 @@ class MediaGallery extends PureComponent { onToggleVisibility: PropTypes.func, }; - static defaultProps = { - standalone: false, - }; - state = { visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'), width: this.props.defaultWidth, @@ -295,7 +290,7 @@ class MediaGallery extends PureComponent { } render () { - const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay } = this.props; + const { media, lang, intl, sensitive, defaultWidth, autoplay } = this.props; const { visible } = this.state; const width = this.state.width || defaultWidth; @@ -303,16 +298,16 @@ class MediaGallery extends PureComponent { const style = {}; - if (this.isFullSizeEligible() && (standalone || !cropImages)) { + if (this.isFullSizeEligible()) { style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`; } else { - style.aspectRatio = '16 / 9'; + style.aspectRatio = '3 / 2'; } const size = media.take(4).size; const uncached = media.every(attachment => attachment.get('type') === 'unknown'); - if (standalone && this.isFullSizeEligible()) { + if (this.isFullSizeEligible()) { children = ; } else { children = media.take(4).map((attachment, i) => ); diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx index 756a977224..c65bd494f3 100644 --- a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx +++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx @@ -12,6 +12,7 @@ class PictureInPicturePlaceholder extends PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + aspectRatio: PropTypes.string, }; handleClick = () => { @@ -20,8 +21,10 @@ class PictureInPicturePlaceholder extends PureComponent { }; render () { + const { aspectRatio } = this.props; + return ( -
+
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 8f188a638c..37951d5782 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -19,7 +19,6 @@ import Bundle from '../features/ui/components/bundle'; import { MediaGallery, Video, Audio } from '../features/ui/util/async-components'; import { displayMedia } from '../initial_state'; -import AttachmentList from './attachment_list'; import { Avatar } from './avatar'; import { AvatarOverlay } from './avatar_overlay'; import { DisplayName } from './display_name'; @@ -191,17 +190,35 @@ class Status extends ImmutablePureComponent { this.props.onTranslate(this._properStatus()); }; - renderLoadingMediaGallery () { - return
; + getAttachmentAspectRatio () { + const attachments = this._properStatus().get('media_attachments'); + + if (attachments.getIn([0, 'type']) === 'video') { + return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`; + } else if (attachments.getIn([0, 'type']) === 'audio') { + return '16 / 9'; + } else { + return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2' + } } - renderLoadingVideoPlayer () { - return
; - } + renderLoadingMediaGallery = () => { + return ( +
+ ); + }; - renderLoadingAudioPlayer () { - return
; - } + renderLoadingVideoPlayer = () => { + return ( +
+ ); + }; + + renderLoadingAudioPlayer = () => { + return ( +
+ ); + }; handleOpenVideo = (options) => { const status = this._properStatus(); @@ -426,18 +443,11 @@ class Status extends ImmutablePureComponent { } if (pictureInPicture.get('inUse')) { - media = ; + media = ; } else if (status.get('media_attachments').size > 0) { const language = status.getIn(['translation', 'language']) || status.get('language'); - if (this.props.muted) { - media = ( - - ); - } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { + if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { const attachment = status.getIn(['media_attachments', 0]); const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); @@ -475,11 +485,11 @@ class Status extends ImmutablePureComponent { ); } - } else if (status.get('spoiler_text').length === 0 && status.get('card') && !this.props.muted) { + } else if (status.get('spoiler_text').length === 0 && status.get('card')) { media = ( ; } else { @@ -515,7 +516,10 @@ class Audio extends PureComponent {
diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx index 936dee12e3..b0c90a4302 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx @@ -86,7 +86,6 @@ class Bookmarks extends ImmutablePureComponent { onClick={this.handleHeaderClick} pinned={pinned} multiColumn={multiColumn} - showBackButton /> this.setState({ thumbnailLoaded: true }); render () { - const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props; + const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, blurhash } = this.props; const { thumbnailLoaded } = this.state; return ( - +
-
{publisher ? publisher : }
-
{title ? title : }
-
{typeof sharedTimes === 'number' ? : }
+
{publisher ? {publisher} : }{publishedAt && <> · }
+
{title ? title : }
+
{author && <>{author} }} /> · }{typeof sharedTimes === 'number' ? : }
diff --git a/app/javascript/mastodon/features/explore/links.jsx b/app/javascript/mastodon/features/explore/links.jsx index 8b199bf47c..489ab6dd61 100644 --- a/app/javascript/mastodon/features/explore/links.jsx +++ b/app/javascript/mastodon/features/explore/links.jsx @@ -55,12 +55,16 @@ class Links extends PureComponent {
{banner} - {isLoading ? () : links.map(link => ( + {isLoading ? () : links.map((link, i) => ( - + diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index d23bbf16a7..29a12c87bf 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -12,6 +12,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { useBlurhash } from 'mastodon/initial_state'; const IDNA_PREFIX = 'xn--'; @@ -57,14 +58,9 @@ export default class Card extends PureComponent { static propTypes = { card: ImmutablePropTypes.map, onOpenMedia: PropTypes.func.isRequired, - compact: PropTypes.bool, sensitive: PropTypes.bool, }; - static defaultProps = { - compact: false, - }; - state = { previewLoaded: false, embedded: false, @@ -148,7 +144,7 @@ export default class Card extends PureComponent { } render () { - const { card, compact } = this.props; + const { card } = this.props; const { embedded, revealed } = this.state; if (card === null) { @@ -156,29 +152,27 @@ export default class Card extends PureComponent { } const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); - const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded; const interactive = card.get('type') !== 'link'; - const className = classnames('status-card', { horizontal, compact, interactive }); - const title = interactive ? {card.get('title')} : {card.get('title')}; const language = card.get('language') || ''; const description = ( -
- {title} - {!(horizontal || compact) &&

{card.get('description')}

} - {provider} +
+ + {provider} + {card.get('published_at') && <> · } + + {card.get('title')} + {card.get('author_name').length > 0 && {card.get('author_name')} }} />}
); const thumbnailStyle = { - visibility: revealed? null : 'hidden', + visibility: revealed ? null : 'hidden', + aspectRatio: `${card.get('width')} / ${card.get('height')}` }; - if (horizontal) { - thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`; - } + let embed; - let embed = ''; let canvas = ( ); + let thumbnail = ; + let spoilerButton = ( ); + spoilerButton = (
{spoilerButton} @@ -219,19 +219,20 @@ export default class Card extends PureComponent {
- {horizontal && } +
)} + {!revealed && spoilerButton}
); } return ( -
+
{embed} - {!compact && description} + {description}
); } else if (card.get('image')) { @@ -243,14 +244,14 @@ export default class Card extends PureComponent { ); } else { embed = ( -
+
); } return ( - + {embed} {description} diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index ddda6eaac6..7348905c53 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -113,8 +113,30 @@ class DetailedStatus extends ImmutablePureComponent { onTranslate(status); }; + _properStatus () { + const { status } = this.props; + + if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { + return status.get('reblog'); + } else { + return status; + } + } + + getAttachmentAspectRatio () { + const attachments = this._properStatus().get('media_attachments'); + + if (attachments.getIn([0, 'type']) === 'video') { + return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`; + } else if (attachments.getIn([0, 'type']) === 'audio') { + return '16 / 9'; + } else { + return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2' + } + } + render () { - const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; + const status = this._properStatus(); const outerStyle = { boxSizing: 'border-box' }; const { intl, compact, pictureInPicture } = this.props; @@ -136,7 +158,7 @@ class DetailedStatus extends ImmutablePureComponent { const language = status.getIn(['translation', 'language']) || status.get('language'); if (pictureInPicture.get('inUse')) { - media = ; + media = ; } else if (status.get('media_attachments').size > 0) { if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { const attachment = status.getIn(['media_attachments', 0]); @@ -167,13 +189,13 @@ class DetailedStatus extends ImmutablePureComponent {