From 0ee2f2e8b9344960f31b9f8a8cf74e894078d864 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 4 Dec 2020 15:40:42 +0000 Subject: [PATCH 1/5] update pghero to 2.7.0 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index d64bfce8..143a8105 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'thor', '~> 0.20' gem 'hamlit-rails', '~> 0.2' gem 'pg', '~> 1.2.3' gem 'makara', '~> 0.4' -gem 'pghero', '~> 2.4.2' +gem 'pghero', '~> 2.7.0' gem 'dotenv-rails', '~> 2.7' gem 'aws-sdk-s3', '~> 1.41', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 0016fb70..16959634 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -401,7 +401,7 @@ GEM equatable (~> 0.5.0) tty-color (~> 0.4.0) pg (1.2.3) - pghero (2.4.2) + pghero (2.7.0) activerecord (>= 5) pkg-config (1.3.7) premailer (1.11.1) @@ -722,7 +722,7 @@ DEPENDENCIES paperclip-av-transcoder (~> 0.6) parallel_tests (~> 2.29) pg (~> 1.2.3) - pghero (~> 2.4.2) + pghero (~> 2.7.0) pkg-config (~> 1.3) posix-spawn! premailer-rails From ed900a47ca359ae96195d3a383338d1040bed47e Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Sat, 5 Dec 2020 23:50:34 -0500 Subject: [PATCH 2/5] Added Table, TableColumnHeader, TableRow components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added: - Table, TableColumnHeader, TableRow components --- app/javascript/gabsocial/components/table.js | 58 +++++++++++++++++ .../components/table_column_header.js | 46 ++++++++++++++ .../gabsocial/components/table_row.js | 63 +++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 app/javascript/gabsocial/components/table.js create mode 100644 app/javascript/gabsocial/components/table_column_header.js create mode 100644 app/javascript/gabsocial/components/table_row.js diff --git a/app/javascript/gabsocial/components/table.js b/app/javascript/gabsocial/components/table.js new file mode 100644 index 00000000..9e8a4cdf --- /dev/null +++ b/app/javascript/gabsocial/components/table.js @@ -0,0 +1,58 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Text from './text' +import TableColumnHeader from './table_column_header' +import TableRow from './table_row' + +class Table extends React.PureComponent { + + render() { + const { + id, + columns, + rows, + } = this.props + + return ( + + { + Array.isArray(columns) && + + { + columns.map((column, i) => ( + + )) + } + + } + { + Array.isArray(rows) && + rows.map((row, i) => ( + + )) + } +
+ ) + } + +} + +Table.propTypes = { + id: PropTypes.string, + columns: PropTypes.array, + rows: PropTypes.array, +} + +export default Table \ No newline at end of file diff --git a/app/javascript/gabsocial/components/table_column_header.js b/app/javascript/gabsocial/components/table_column_header.js new file mode 100644 index 00000000..77f20beb --- /dev/null +++ b/app/javascript/gabsocial/components/table_column_header.js @@ -0,0 +1,46 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { CX } from '../constants' +import Text from './text' + +class TableColumnHeader extends React.PureComponent { + + render() { + const { + column, + total, + index, + } = this.props + + const isLast = index === total - 1 + const classes = CX({ + d: 1, + px15: 1, + py10: 1, + borderRight1PX: !isLast, + borderColorSecondary: !isLast, + }) + + const style = { + width: `${100 / total}%` + } + + return ( + + + {column} + + + ) + } + +} + +TableColumnHeader.propTypes = { + column: PropTypes.object, + index: PropTypes.number, + total: PropTypes.number, + tableId: PropTypes.string, +} + +export default TableColumnHeader \ No newline at end of file diff --git a/app/javascript/gabsocial/components/table_row.js b/app/javascript/gabsocial/components/table_row.js new file mode 100644 index 00000000..de3b918d --- /dev/null +++ b/app/javascript/gabsocial/components/table_row.js @@ -0,0 +1,63 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { CX } from '../constants' +import Text from './text' + +class TableRow extends React.PureComponent { + + render() { + const { + id, + row, + total, + index, + } = this.props + + if (!Array.isArray(row)) return null + + const isLast = index === total - 1 + const classes = CX({ + d: 1, + flexRow: 1, + borderBottom1PX: !isLast, + borderColorSecondary: !isLast, + }) + + const style = { + width: `${100 / row.length}%` + } + + return ( + + { + row.map((item, i) => { + const itemClasses = CX({ + d: 1, + px15: 1, + py10: 1, + borderRight1PX: i !== row.length - 1, + borderColorSecondary: i !== row.length - 1, + }) + return ( + + + {row[i]} + + + ) + }) + } + + ) + } + +} + +TableRow.propTypes = { + row: PropTypes.object, + tableId: PropTypes.string, + total: PropTypes.number, + index: PropTypes.number, +} + +export default TableRow \ No newline at end of file From d1eb3d09e402f03261e079da11c7518fb061e41a Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Sat, 5 Dec 2020 23:52:13 -0500 Subject: [PATCH 3/5] Added CCPC pages in About pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added: - CCPC pages in About pages --- .../about/california_consumer_protection.js | 378 ++++++++++++++++++ .../california_consumer_protection_contact.js | 42 ++ .../features/about/privacy_policy.js | 34 ++ app/javascript/gabsocial/features/ui/ui.js | 4 + .../features/ui/util/async_components.js | 2 + .../gabsocial/layouts/about_layout.js | 4 + 6 files changed, 464 insertions(+) create mode 100644 app/javascript/gabsocial/features/about/california_consumer_protection.js create mode 100644 app/javascript/gabsocial/features/about/california_consumer_protection_contact.js diff --git a/app/javascript/gabsocial/features/about/california_consumer_protection.js b/app/javascript/gabsocial/features/about/california_consumer_protection.js new file mode 100644 index 00000000..ea3b5fd1 --- /dev/null +++ b/app/javascript/gabsocial/features/about/california_consumer_protection.js @@ -0,0 +1,378 @@ +import React from 'react' +import Block from '../../components/block' +import Button from '../../components/button' +import Divider from '../../components/divider' +import Heading from '../../components/heading' +import Text from '../../components/text' +import Table from '../../components/table' + +export default class CaliforniaConsumerProtection extends React.PureComponent { + + render() { + + return ( +
+ +
+ GAB AI INC +
+ Privacy Policy for California Residents + Effective Date: 2 December 2020 + Last Updated on: 2 December 2020 +
+ + This Privacy Policy for California Residents supplements the information contained in the Company's privacy policy and applies solely to all visitors, users, and others who reside in the State of California ("consumers" or "you"). We adopt this notice to comply with the California Consumer Privacy Act of 2018 (CCPA) and any terms defined in the CCPA have the same meaning when used in this Policy. + +
+ Information We Collect + We collect information that identifies, relates to, describes, references, is reasonably capable of being associated with, or could reasonably be linked, directly or indirectly, with a particular consumer, household, or device ("personal information"). Personal information does not include: + +
    +
  • + Publicly available information from government records. +
  • +
  • + Deidentified or aggregated consumer information. +
  • +
  • + Information excluded from the CCPA's scope, like: + +
      +
    • + health or medical information covered by the Health Insurance Portability and Accountability Act of 1996 (HIPAA) and the California Confidentiality of Medical Information Act (CMIA), clinical trial data, or other qualifying research data; +
    • +
    • + personal information covered by certain sector-specific privacy laws, including the Fair Credit Reporting Act (FCRA), the Gramm-Leach-Bliley Act (GLBA) or California Financial Information Privacy Act (FIPA), and the Driver's Privacy Protection Act of 1994. +
    • +
    +
  • +
+ + In particular, we have collected the following categories of personal information from consumers within the last twelve (12) months: + +
+ + + We obtain the categories of personal information listed above from the following categories of sources: +
    +
  • + Directly from you. For example, from forms you complete, video, photos, and text information which you post, products and services you purchase, and communications you make with us. +
  • +
  • + Indirectly from you. For example, from observing your actions on our Website. +
  • +
+ +
+ Use of Personal Information + We do not sell user information. We may use or disclose the personal information we collect for one or more of the following purposes: +
    +
  • + To fulfill or meet the reason you provided the information. For example, if you share your name and contact information to request a price quote or ask a question about our products or services, we will use that personal information to respond to your inquiry. If you provide your personal information to purchase a product or service, we will use that information to process your payment and facilitate delivery. We may also save your information to facilitate new product orders or process returns. +
  • +
  • + To provide, support, personalize, and develop our Website, products, and services. +
  • +
  • + To create, maintain, customize, and secure your account with us. +
  • +
  • + To process your requests, purchases, transactions, and payments and prevent transactional fraud. +
  • +
  • + To provide you with support and to respond to your inquiries, including to investigate and address your concerns and monitor and improve our responses. +
  • +
  • + To personalize your Website experience and to deliver content and product and service offerings relevant to your interests, including targeted offers and ads through our Website, third-party sites, and via email or text message (with your consent, where required by law). +
  • +
  • + To help maintain the safety, security, and integrity of our Website, products and services, databases and other technology assets, and business. +
  • +
  • + For testing, research, analysis, and product development, including to develop and improve our Website, products, and services. +
  • +
  • + To respond to law enforcement requests and as required by applicable law, court order, or governmental regulations. +
  • +
  • + As described to you when collecting your personal information or as otherwise set forth in the CCPA. +
  • +
  • + To evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which personal information held by us about our Website and our users is among the assets transferred. +
  • +
+ + We will not collect additional categories of personal information or use the personal information we collected for materially different, unrelated, or incompatible purposes without providing you notice. + +
+ Sharing Personal Information + We may share your personal information by disclosing it to a third party for a business purpose. We only make these business purpose disclosures under written contracts that describe the purposes, require the recipient to keep the personal information confidential, and prohibit using the disclosed information for any purpose except performing the contract. In the preceding twelve (12) months, Company has disclosed personal information for a business purpose to the categories of third parties indicated in the chart below. + We do not sell your personal information. For more on your personal information sale rights, see Personal Information Sales Opt-Out and Opt-In Rights. + +
+ + Reselling Personal Information + The CCPA prohibits a third party from reselling personal information unless you have received explicit notice and an opportunity to opt-out of further sales. We do not sell your personal information and to our knowledge none of our service providers sell your personal information. If at any future time we do sell users’ personal information you will be afforded the opportunity to opt-out and we will provide a clear and conspicuous link on Gab.com that will enable you to opt-out. + +
+ Your Rights and Choices + The CCPA provides consumers (California residents) with specific rights regarding their personal information. This section describes your CCPA rights and explains how to exercise those rights. + Right to Know and Data Portability + You have the right to request that we disclose certain information to you about our collection and use of your personal information over the past 12 months (the "right to know"). Once we receive your request and confirm your identity (see Exercising Your Rights to Know or Delete), we will disclose to you: + +
    +
  • + The categories of personal information we collected about you. +
  • +
  • + The categories of sources for the personal information we collected about you. +
  • +
  • + Our business or commercial purpose for collecting or selling that personal information. +
  • +
  • + The categories of third parties with whom we share that personal information. +
  • +
  • + If we sold or disclosed your personal information for a business purpose, two separate lists disclosing: +
      +
    • + sales, identifying the personal information categories that each category of recipient purchased; and +
    • +
    • + disclosures for a business purpose, identifying the personal information categories that each category of recipient obtained. +
    • +
    +
  • +
  • + The specific pieces of personal information we collected about you (also called a data portability request). +
  • +
+ + Right to Delete + You have the right to request that we delete any of your personal information that we collected from you and retained, subject to certain exceptions (the "right to delete"). Once we receive your request and confirm your identity (see Exercising Your Rights to Know or Delete), we will review your request to see if an exception allowing us to retain the information applies. We may deny your deletion request if retaining the information is necessary for us or our service provider(s) to: +
    +
  1. + Complete the transaction for which we collected the personal information, provide a good or service that you requested, take actions reasonably anticipated within the context of our ongoing business relationship with you, fulfill the terms of a written warranty or product recall conducted in accordance with federal law, or otherwise perform our contract with you. +
  2. +
  3. + Detect security incidents, protect against malicious, deceptive, fraudulent, or illegal activity, or prosecute those responsible for such activities. +
  4. +
  5. + Debug products to identify and repair errors that impair existing intended functionality. +
  6. +
  7. + Exercise free speech, ensure the right of another consumer to exercise their free speech rights, or exercise another right provided for by law. +
  8. +
  9. + Comply with the California Electronic Communications Privacy Act (Cal. Penal Code § 1546 et. seq.). +
  10. +
  11. + Engage in public or peer-reviewed scientific, historical, or statistical research in the public interest that adheres to all other applicable ethics and privacy laws, when the information's deletion may likely render impossible or seriously impair the research's achievement, if you previously provided informed consent. +
  12. +
  13. + Enable solely internal uses that are reasonably aligned with consumer expectations based on your relationship with us. +
  14. +
  15. + Comply with a legal obligation. +
  16. +
  17. + Make other internal and lawful uses of that information that are compatible with the context in which you provided it. +
  18. +
+ + We will delete or deidentify personal information not subject to one of these exceptions from our records and will direct our service providers to take similar action. + Exercising Your Rights to Know or Delete + + To exercise your rights to know or delete described above, please submit a request by (preferred) e-mailing us at  + +  ; sending a request to us via our CCPA portal at  + + , or or mailing us at + + + Gab AI Inc.
+ 700 N State Street
+ Clarks Summit, PA 18411
+ United States of America +
+ + Only you, or someone legally authorized to act on your behalf, may make a request to know or delete related to your personal information. + You may also make a request to know or delete on behalf of your child, in which case we will require proof of identity and proof of account ownership. + You may only submit a request to know twice within a 12-month period. Your request to know or delete must: +
    +
  • + Provide sufficient information that allows us to reasonably verify you are the person about whom we collected personal information or an authorized representative. +
  • +
  • + Describe your request with sufficient detail that allows us to properly understand, evaluate, and respond to it. +
  • +
+ + We cannot respond to your request or provide you with personal information if we cannot verify your identity or authority to make the request and confirm the personal information relates to you. + You do not need to create an account with us to submit a request to know or delete. However, we do consider requests made through your password protected account sufficiently verified when the request relates to personal information associated with that specific account. + We will only use personal information provided in the request to verify the requestor's identity or authority to make it. + For instructions on exercising your sale opt-out or opt-in rights, see Personal Information Sales Opt-Out and Opt-In Rights. + + Response Timing and Format + We will confirm receipt of your request within ten (10) business days. If you do not receive confirmation within the 10-day timeframe, please contact us at  + + . + + We endeavor to substantively respond to a verifiable consumer request within forty-five (45) days of its receipt. If we require more time (up to another 45 days), we will inform you of the reason and extension period in writing. + If you have an account with us, we will deliver our written response to that account[ via the contact address associated with that account]. If you do not have an account with us, we will deliver our written response by mail or electronically, at your option. + Any disclosures we provide will only cover the 12-month period preceding our receipt of your request. The response we provide will also explain the reasons we cannot comply with a request, if applicable. For data portability requests, we will select a format to provide your personal information that is readily useable and should allow you to transmit the information from one entity to another entity without hindrance. + We do not charge a fee to process or respond to your verifiable consumer request unless it is excessive, repetitive, or manifestly unfounded. If we determine that the request warrants a fee, we will tell you why we made that decision and provide you with a cost estimate before completing your request. + + Personal Information Sales Opt-Out and Opt-In Rights + If you are age 16 or older, you have the right to direct us to not sell your personal information at any time (the "right to opt-out"). We do not sell the personal information of any user. If in the future we decide to sell personal information, we will afford you with a right to opt-out and will provide an opt-out link in this CCPA Privacy Policy. + +
+ Non-Discrimination + You have a right not to receive discriminatory treatment for exercising your CCPA rights and we will not discriminate against you for exercising any of your CCPA rights. Unless permitted by the CCPA, we will not: +
    +
  • + Deny you goods or services. +
  • +
  • + Charge you different prices or rates for goods or services, including through granting discounts or other benefits, or imposing penalties. +
  • +
  • + Provide you a different level or quality of goods or services. +
  • +
  • + Suggest that you may receive a different price or rate for goods or services or a different level or quality of goods or services. +
  • +
+ +
+ CCPA Rights Request Met + Metrics regarding the consumer rights requests we received from California residents from January 1, 2019 to December 31, 2019 appear in the following chart: +
+ +
+ Other California Privacy Rights + California's "Shine the Light" law (Civil Code Section § 1798.83) permits users of our Website that are California residents to request certain information regarding our disclosure of personal information to third parties for their direct marketing purposes. To make such a request, please write to us at Gab AI Inc., 700 N State Street, Clarks Summit, PA, 18411. + +
+ Changes to Our Privacy Policy + We reserve the right to amend this privacy policy at our discretion and at any time. When we make changes to this privacy policy, we will post the updated notice on the Website and update the notice's effective date. Your continued use of our Website following the posting of changes constitutes your acceptance of such changes. + +
+ Contact Information + If you have any questions or comments about this notice, the ways in which Gab collects and uses your information described here and in the Privacy Policy, your choices and rights regarding such use, or wish to exercise your rights under California law, please do not hesitate to contact us at: + + + Contact form: + If you have any questions or comments about this notice, the ways in which Gab collects and uses your information described here and in the Privacy Policy, your choices and rights regarding such use, or wish to exercise your rights under California law, please do not hesitate to contact us at: + + + + Email: + + + + + Postal Address: + Gab AI Inc.
+ 700 N State Street
+ Clarks Summit, PA 18411
+ Attn: Data Privacy Department +
+ + If you need to access this Policy in an alternative format due to having a disability, please contact  + + + + + + + ) + } + +} diff --git a/app/javascript/gabsocial/features/about/california_consumer_protection_contact.js b/app/javascript/gabsocial/features/about/california_consumer_protection_contact.js new file mode 100644 index 00000000..47bf342e --- /dev/null +++ b/app/javascript/gabsocial/features/about/california_consumer_protection_contact.js @@ -0,0 +1,42 @@ +import React from 'react' +import Block from '../../components/block' +import Button from '../../components/button' +import Divider from '../../components/divider' +import Heading from '../../components/heading' +import Text from '../../components/text' +import Table from '../../components/table' + +export default class CaliforniaConsumerProtectionContact extends React.PureComponent { + + render() { + + return ( +
+ +
+ GAB AI INC +
+ Contact form for Data Privacy +
+ + + Email: + + + +
+
+
+ ) + } + +} diff --git a/app/javascript/gabsocial/features/about/privacy_policy.js b/app/javascript/gabsocial/features/about/privacy_policy.js index e921ec26..af88941a 100644 --- a/app/javascript/gabsocial/features/about/privacy_policy.js +++ b/app/javascript/gabsocial/features/about/privacy_policy.js @@ -278,6 +278,40 @@ export default class PrivacyPolicy extends React.PureComponent { Changes to Our Privacy PolicyIt is our policy to post any changes we make to our privacy policy on this page with a notice that the privacy policy has been updated on the Website home page. If we make material changes to how we treat our users’ personal information, we will notify you through a notice on the Website home page. The date the privacy policy was last revised is identified at the top of the page. You are responsible for ensuring we have an up-to-date active and deliverable email address for you, and for periodically visiting our Website and this privacy policy to check for any changes. +
+ Your California Privacy Rights + If you are a California resident, California law may provide you with additional rights regarding our use of your personal information. To learn more about your California privacy rights, visit our CCPA Privacy Notice at  + + + California's "Shine the Light" law (Civil Code Section § 1798.83) permits users of our Website that are California residents to request certain information regarding our disclosure of personal information to third parties for their direct marketing purposes. To make such a request, please send us an e-mail to  + +  or write to us at + + + Gab AI Inc.
+ 700 N State Street
+ Clarks Summit, PA 18411
+ Attn: Data Privacy Department +
+
Contact InformationTo ask questions or comment about this privacy policy and our privacy practices, contact us at: diff --git a/app/javascript/gabsocial/features/ui/ui.js b/app/javascript/gabsocial/features/ui/ui.js index 0f7fc7f2..d95341d6 100644 --- a/app/javascript/gabsocial/features/ui/ui.js +++ b/app/javascript/gabsocial/features/ui/ui.js @@ -54,6 +54,8 @@ import { BlockedAccounts, BlockedDomains, BookmarkedStatuses, + CaliforniaConsumerProtection, + CaliforniaConsumerProtectionContact, CommunityTimeline, Compose, DMCA, @@ -187,6 +189,8 @@ class SwitchingArea extends React.PureComponent { + + diff --git a/app/javascript/gabsocial/features/ui/util/async_components.js b/app/javascript/gabsocial/features/ui/util/async_components.js index 5986548e..f885ed52 100644 --- a/app/javascript/gabsocial/features/ui/util/async_components.js +++ b/app/javascript/gabsocial/features/ui/util/async_components.js @@ -7,6 +7,8 @@ export function BlockAccountModal() { return import(/* webpackChunkName: "compon export function BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') } export function BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') } export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') } +export function CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') } +export function CaliforniaConsumerProtectionContact() { return import(/* webpackChunkName: "features/california_consumer_protection_contact" */'../../about/california_consumer_protection_contact') } export function CommentSortingOptionsPopover() { return import(/* webpackChunkName: "components/comment_sorting_options_popover" */'../../../components/popover/comment_sorting_options_popover') } export function CommunityTimeline() { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline') } export function CommunityTimelineSettingsModal() { return import(/* webpackChunkName: "components/community_timeline_settings_modal" */'../../../components/modal/community_timeline_settings_modal') } diff --git a/app/javascript/gabsocial/layouts/about_layout.js b/app/javascript/gabsocial/layouts/about_layout.js index a16a74ae..d1fac708 100644 --- a/app/javascript/gabsocial/layouts/about_layout.js +++ b/app/javascript/gabsocial/layouts/about_layout.js @@ -53,6 +53,10 @@ class AboutLayout extends React.PureComponent { title: 'Terms of Service', to: '/about/tos', }, + { + title: 'California Consumer Protection', + to: '/about/ccpa', + }, ] } From 95d326936be226b5bc443724a376bd1bdf5713a7 Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Sun, 6 Dec 2020 11:16:04 -0800 Subject: [PATCH 4/5] [media] Force media attachments query from master on status post, change home query --- app/models/status.rb | 4 ++-- app/services/post_status_service.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/status.rb b/app/models/status.rb index 7e3dbe8e..a36980e8 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -302,13 +302,13 @@ class Status < ApplicationRecord end def as_home_timeline(account) - query = where('updated_at > ?', 5.days.ago) + query = where('created_at > ?', 5.days.ago) query.where(visibility: [:public, :unlisted, :private]) query.where(account: [account] + account.following).without_replies end def as_group_timeline(group) - query = where('updated_at > ?', 5.days.ago) + query = where('created_at > ?', 5.days.ago) query.where(group: group).without_replies end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 0f150bc6..1af33e7f 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -120,6 +120,7 @@ class PostStatusService < BaseService raise GabSocial::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present? + @account.media_attachments.connection.stick_to_master! @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) hasVideoOrGif = @media.find(&:video?) || @media.find(&:gifv?) From c18991f174c2202aeeb5c1cf92358ed2568153e2 Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Tue, 8 Dec 2020 23:19:10 -0500 Subject: [PATCH 5/5] Added LinkBlock functionality in admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added: - LinkBlock functionality in admin --- .../admin/link_blocks_controller.rb | 47 +++++++++++++++++++ app/lib/tag_manager.rb | 6 +++ app/models/concerns/link_normalizable.rb | 15 ++++++ app/models/link_block.rb | 26 ++++++++++ app/policies/link_block_policy.rb | 15 ++++++ app/services/edit_status_service.rb | 5 ++ app/services/post_status_service.rb | 7 ++- .../admin/link_blocks/_link_block.html.haml | 5 ++ app/views/admin/link_blocks/index.html.haml | 14 ++++++ app/views/admin/link_blocks/new.html.haml | 11 +++++ config/locales/en.yml | 10 ++++ config/locales/en_GB.yml | 10 ++++ config/navigation.rb | 1 + config/routes.rb | 1 + .../20201206060226_create_link_blocks.rb | 10 ++++ db/schema.rb | 9 +++- 16 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/link_blocks_controller.rb create mode 100644 app/models/concerns/link_normalizable.rb create mode 100644 app/models/link_block.rb create mode 100644 app/policies/link_block_policy.rb create mode 100644 app/views/admin/link_blocks/_link_block.html.haml create mode 100644 app/views/admin/link_blocks/index.html.haml create mode 100644 app/views/admin/link_blocks/new.html.haml create mode 100644 db/migrate/20201206060226_create_link_blocks.rb diff --git a/app/controllers/admin/link_blocks_controller.rb b/app/controllers/admin/link_blocks_controller.rb new file mode 100644 index 00000000..50ac5c88 --- /dev/null +++ b/app/controllers/admin/link_blocks_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Admin + class LinkBlocksController < BaseController + before_action :set_link_block, only: [:show, :destroy] + + def index + authorize :link_block, :index? + @link_blocks = LinkBlock.page(params[:page]) + end + + def new + authorize :link_block, :create? + @link_block = LinkBlock.new + end + + def create + authorize :link_block, :create? + + @link_block = LinkBlock.new(resource_params) + + if @link_block.save + log_action :create, @link_block + redirect_to admin_link_blocks_path, notice: I18n.t('admin.link_blocks.created_msg') + else + render :new + end + end + + def destroy + authorize @link_block, :destroy? + @link_block.destroy! + log_action :destroy, @link_block + redirect_to admin_link_blocks_path, notice: I18n.t('admin.link_blocks.destroyed_msg') + end + + private + + def set_link_block + @link_block = LinkBlock.find(params[:id]) + end + + def resource_params + params.require(:link_block).permit(:link) + end + end +end diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index fb364cb9..c3b20aee 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -22,6 +22,12 @@ class TagManager uri.normalized_host end + def normalize_link(link) + return if link.nil? + uri = Addressable::URI.parse(link) + return "#{uri.normalized_host}#{uri.normalized_path}" + end + def same_acct?(canonical, needle) return true if canonical.casecmp(needle).zero? username, domain = needle.split('@') diff --git a/app/models/concerns/link_normalizable.rb b/app/models/concerns/link_normalizable.rb new file mode 100644 index 00000000..967d40be --- /dev/null +++ b/app/models/concerns/link_normalizable.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module LinkNormalizable + extend ActiveSupport::Concern + + included do + before_validation :normalize_link + end + + private + + def normalize_link + self.link = TagManager.instance.normalize_link(link&.strip) + end +end diff --git a/app/models/link_block.rb b/app/models/link_block.rb new file mode 100644 index 00000000..6766e1b7 --- /dev/null +++ b/app/models/link_block.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: link_blocks +# +# id :bigint(8) not null, primary key +# link :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class LinkBlock < ApplicationRecord + include LinkNormalizable + + validates :link, presence: true, uniqueness: true + + def self.block?(text) + return false if text.nil? + return false if text.length < 1 + + urls = text.scan(FetchLinkCardService::URL_PATTERN).map { |array| Addressable::URI.parse(array[0]).normalize } + url = urls.first + link_for_fetch = TagManager.instance.normalize_link(url) + where(link: link_for_fetch).exists? + end +end \ No newline at end of file diff --git a/app/policies/link_block_policy.rb b/app/policies/link_block_policy.rb new file mode 100644 index 00000000..8b47dc14 --- /dev/null +++ b/app/policies/link_block_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class LinkBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/services/edit_status_service.rb b/app/services/edit_status_service.rb index 16d7015e..561fee88 100644 --- a/app/services/edit_status_service.rb +++ b/app/services/edit_status_service.rb @@ -25,6 +25,7 @@ class EditStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + validate_links! validate_media! preprocess_attributes! revision_text = prepare_revision_text @@ -89,6 +90,10 @@ class EditStatusService < BaseService raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif end + def validate_links! + raise GabSocial::NotPermittedError if LinkBlock.block?(@text) + end + def language_from_option(str) ISO_639.find(str)&.alpha2 end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 1af33e7f..601bf2b9 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -34,6 +34,7 @@ class PostStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + validate_links! validate_media! validate_group! preprocess_attributes! @@ -98,7 +99,7 @@ class PostStatusService < BaseService end def postprocess_status! - LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text? + LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) # Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id) # ActivityPub::DistributionWorker.perform_async(@status.id) @@ -127,6 +128,10 @@ class PostStatusService < BaseService raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif end + def validate_links! + raise GabSocial::NotPermittedError if LinkBlock.block?(@text) + end + def language_from_option(str) ISO_639.find(str)&.alpha2 end diff --git a/app/views/admin/link_blocks/_link_block.html.haml b/app/views/admin/link_blocks/_link_block.html.haml new file mode 100644 index 00000000..845b3be3 --- /dev/null +++ b/app/views/admin/link_blocks/_link_block.html.haml @@ -0,0 +1,5 @@ +%tr + %td + %samp= link_block.link + %td + = table_link_to 'trash', t('admin.link_blocks.delete'), admin_link_block_path(link_block), method: :delete diff --git a/app/views/admin/link_blocks/index.html.haml b/app/views/admin/link_blocks/index.html.haml new file mode 100644 index 00000000..2b834df0 --- /dev/null +++ b/app/views/admin/link_blocks/index.html.haml @@ -0,0 +1,14 @@ +- content_for :page_title do + = t('admin.link_blocks.title') + +.table-wrapper + %table.table + %thead + %tr + %th= t('admin.link_blocks.link') + %th + %tbody + = render @link_blocks + += paginate @link_blocks += link_to t('admin.link_blocks.add_new'), new_admin_link_block_path, class: 'button' diff --git a/app/views/admin/link_blocks/new.html.haml b/app/views/admin/link_blocks/new.html.haml new file mode 100644 index 00000000..cc19d9e0 --- /dev/null +++ b/app/views/admin/link_blocks/new.html.haml @@ -0,0 +1,11 @@ +- content_for :page_title do + = t('.title') + += simple_form_for @link_block, url: admin_link_blocks_path do |f| + = render 'shared/error_messages', object: @link_block + + .fields-group + = f.input :link, wrapper: :with_label, label: t('admin.link_blocks.link') + + .actions + = f.button :button, t('.create'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index ddab0e43..68692541 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -325,6 +325,16 @@ en: title: Undo domain block for %{domain} undo: Undo undo: Undo domain block + link_blocks: + add_new: Add new + created_msg: Successfully added link to blacklist + delete: Delete + destroyed_msg: Successfully deleted link from blacklist + link: Link + new: + create: Add link + title: New link blacklist entry + title: Link blacklist email_domain_blocks: add_new: Add new created_msg: Successfully added e-mail domain to blacklist diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index 4e3ed84f..60044551 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -292,6 +292,16 @@ en_GB: title: Undo domain block for %{domain} undo: Undo undo: Undo domain block + link_blocks: + add_new: Add new + created_msg: Successfully added link to blacklist + delete: Delete + destroyed_msg: Successfully deleted link from blacklist + link: Link + new: + create: Add link + title: New link blacklist entry + title: Link blacklist email_domain_blocks: add_new: Add new created_msg: Successfully added e-mail domain to blacklist diff --git a/config/navigation.rb b/config/navigation.rb index 414c179d..028d8bc7 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -37,6 +37,7 @@ SimpleNavigation::Configuration.run do |navigation| s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.admin? } s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } + s.item :link_blocks, safe_join([fa_icon('link fw'), t('admin.link_blocks.title')]), admin_link_blocks_url, highlights_on: %r{/admin/link_blocks}, if: -> { current_user.admin? } end n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s| diff --git a/config/routes.rb b/config/routes.rb index 8b089dc6..c8747409 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -153,6 +153,7 @@ Rails.application.routes.draw do resources :subscriptions, only: [:index] resources :domain_blocks, only: [:new, :create, :show, :destroy] resources :email_domain_blocks, only: [:index, :new, :create, :destroy] + resources :link_blocks, only: [:index, :new, :create, :destroy] resources :action_logs, only: [:index] resources :warning_presets, except: [:new] resource :settings, only: [:edit, :update] diff --git a/db/migrate/20201206060226_create_link_blocks.rb b/db/migrate/20201206060226_create_link_blocks.rb new file mode 100644 index 00000000..af47466b --- /dev/null +++ b/db/migrate/20201206060226_create_link_blocks.rb @@ -0,0 +1,10 @@ +class CreateLinkBlocks < ActiveRecord::Migration[5.2] + def change + create_table :link_blocks do |t| + t.string :link, null: false, default: '' + t.timestamps null: false + end + + add_index :link_blocks, :link, unique: true + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 6fa418c8..d2aaba61 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_09_08_210104) do +ActiveRecord::Schema.define(version: 2020_12_06_060226) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -432,6 +432,13 @@ ActiveRecord::Schema.define(version: 2020_09_08_210104) do t.index ["user_id"], name: "index_invites_on_user_id" end + create_table "link_blocks", force: :cascade do |t| + t.string "link", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["link"], name: "index_link_blocks_on_link", unique: true + end + create_table "list_accounts", force: :cascade do |t| t.bigint "list_id", null: false t.bigint "account_id", null: false