Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into feature/removing_ruby_junk

This commit is contained in:
mgabdev 2020-12-09 00:00:35 -05:00
commit 586935f07f
28 changed files with 827 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 (
<table className={[_s.d, _s.border1PX, _s.borderColorSecondary].join(' ')}>
{
Array.isArray(columns) &&
<tr className={[_s.d, _s.flexRow, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
{
columns.map((column, i) => (
<TableColumnHeader
tableId={id}
column={column}
key={`table-${id}-column-header-${i}`}
total={columns.length}
index={i}
/>
))
}
</tr>
}
{
Array.isArray(rows) &&
rows.map((row, i) => (
<TableRow
tableId={id}
row={row}
key={`table-${id}-row-${i}`}
total={rows.length}
index={i}
/>
))
}
</table>
)
}
}
Table.propTypes = {
id: PropTypes.string,
columns: PropTypes.array,
rows: PropTypes.array,
}
export default Table

View File

@ -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 (
<th className={classes} style={style}>
<Text size='medium' weight='medium'>
{column}
</Text>
</th>
)
}
}
TableColumnHeader.propTypes = {
column: PropTypes.object,
index: PropTypes.number,
total: PropTypes.number,
tableId: PropTypes.string,
}
export default TableColumnHeader

View File

@ -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 (
<tr className={classes}>
{
row.map((item, i) => {
const itemClasses = CX({
d: 1,
px15: 1,
py10: 1,
borderRight1PX: i !== row.length - 1,
borderColorSecondary: i !== row.length - 1,
})
return (
<td className={itemClasses} style={style} key={`row-data-${id}-${i}`}>
<Text>
{row[i]}
</Text>
</td>
)
})
}
</tr>
)
}
}
TableRow.propTypes = {
row: PropTypes.object,
tableId: PropTypes.string,
total: PropTypes.number,
index: PropTypes.number,
}
export default TableRow

View File

@ -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 (
<div className={[_s.d].join(' ')}>
<Block>
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
<Heading>GAB AI INC</Heading>
<br />
<Heading>Privacy Policy for California Residents</Heading>
<Heading size='h4'>Effective Date: 2 December 2020</Heading>
<Heading size='h4'>Last Updated on: 2 December 2020</Heading>
<br />
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<br />
<Heading size='h2'>Information We Collect</Heading>
<Text tagName='p' className={_s.mt15} size='medium'>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:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>Publicly available information from government records.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Deidentified or aggregated consumer information.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Information excluded from the CCPA's scope, like:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>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;</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>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.</Text>
</li>
</ul>
</li>
</ul>
<Text tagName='p' className={_s.mt15} size='medium'>In particular, we have collected the following categories of personal information from consumers within the last twelve (12) months: </Text>
<br />
<Table
id='table-1'
columns={['Category', 'Examples', 'Collected']}
rows={[
['A. Identifiers.', "A real name, alias, postal address, unique personal identifier, online identifier, Internet Protocol address, email address, account name, Social Security number, driver's license number, passport number, or other similar identifiers.", "Yes."],
["B. Personal information categories listed in the California Customer Records statute (Cal. Civ. Code § 1798.80(e)).", "name, signature, Social Security number, physical characteristics or description, address, telephone number, passport number, driver's license or state identification card number, insurance policy number, education, employment, employment history, bank account number, credit card number, debit card number, or any other financial information, medical information, or health insurance information. Some personal information included in this category may overlap with other categories.", "Yes"],
["C. Protected classification characteristics under California or federal law.", "Age (40 years or older), race, color, ancestry, national origin, citizenship, religion or creed, marital status, medical condition, physical or mental disability, sex (including gender, gender identity, gender expression, pregnancy or childbirth and related medical conditions), sexual orientation, veteran or military status, genetic information (including familial genetic information).", "Yes if you choose to provide it as part of your user profile or user generated content."],
["D. Commercial information.", "Records of personal property, products or services purchased, obtained, or considered, or other purchasing or consuming histories or tendencies.", "Yes."],
["E. Biometric information.", "Genetic, physiological, behavioral, and biological characteristics, or activity patterns used to extract a template or other identifier or identifying information, such as, fingerprints, faceprints, and voiceprints, iris or retina scans, keystroke, gait, or other physical patterns, and sleep, health, or exercise data.", "Yes if you choose to provide it as part of your user profile or user generated content."],
["F. Internet or other similar network activity.", "Browsing history, search history, information on a consumer's interaction with a website, application, or advertisement.", "Yes."],
["G. Geolocation data.", "Physical location or movements or information which might be able to ascertain physical location e.g. IP address.", "Yes."],
["H. Sensory data.", "Audio, electronic, visual, thermal, olfactory, or similar information.", "Yes, if you choose to provide it as part of your user profile or user generated content."],
["I. Professional or employment-related information.", "Current or past job history or performance evaluations.", "Yes, if you choose to provide it as part of your user profile or user generated content."],
["J. Non-public education information (per the Family Educational Rights and Privacy Act (20 U.S.C. Section 1232g, 34 C.F.R. Part 99)).", "Education records directly related to a student maintained by an educational institution or party acting on its behalf, such as grades, transcripts, class lists, student schedules, student identification codes, student financial information, or student disciplinary records.", "Yes, if you choose to provide it as part of their user profile or user generated content."],
["K. Inferences drawn from other personal information.", "Profile reflecting a person's preferences, characteristics, psychological trends, predispositions, behavior, attitudes, intelligence, abilities, and aptitudes.", "No."]
]}
/>
<Text tagName='p' className={_s.mt15} size='medium'>We obtain the categories of personal information listed above from the following categories of sources:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>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.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Indirectly from you. For example, from observing your actions on our Website.</Text>
</li>
</ul>
<br />
<Heading size='h2'>Use of Personal Information</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>We do not sell user information. We may use or disclose the personal information we collect for one or more of the following purposes:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>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.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To provide, support, personalize, and develop our Website, products, and services.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To create, maintain, customize, and secure your account with us.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To process your requests, purchases, transactions, and payments and prevent transactional fraud.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To provide you with support and to respond to your inquiries, including to investigate and address your concerns and monitor and improve our responses.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>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).</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To help maintain the safety, security, and integrity of our Website, products and services, databases and other technology assets, and business.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>For testing, research, analysis, and product development, including to develop and improve our Website, products, and services.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>To respond to law enforcement requests and as required by applicable law, court order, or governmental regulations.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>As described to you when collecting your personal information or as otherwise set forth in the CCPA.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>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.</Text>
</li>
</ul>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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.</Text>
<br />
<Heading size='h2'>Sharing Personal Information</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Table
id='table-2'
columns={['Personal Information Category', 'Business Purpose Disclosures']}
rows={[
['A. Identifiers.', "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["B: California Customer Records personal information categories.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["C: Protected classification characteristics under California or federal law.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["D: Commercial information.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["E: Biometric information", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["F: Internet or other similar network activity.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["G: Geolocation data (such as IP address)", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["H: Sensory data.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["I: Professional or employment-related information.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["J: Non-public education information.", "Affiliates, vendors, consultants and other service providers. Professional advisors and consultants. Law enforcement."],
["K: Inferences drawn from other personal information.", "Not applicable"],
]}
/>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Reselling Personal Information</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<br />
<Heading size='h2'>Your Rights and Choices </Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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.</Text>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Right to Know and Data Portability</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>The categories of personal information we collected about you.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>The categories of sources for the personal information we collected about you.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Our business or commercial purpose for collecting or selling that personal information.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>The categories of third parties with whom we share that personal information.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>If we sold or disclosed your personal information for a business purpose, two separate lists disclosing:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>sales, identifying the personal information categories that each category of recipient purchased; and</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>disclosures for a business purpose, identifying the personal information categories that each category of recipient obtained.</Text>
</li>
</ul>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>The specific pieces of personal information we collected about you (also called a data portability request).</Text>
</li>
</ul>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Right to Delete</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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:</Text>
<ol className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>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.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Detect security incidents, protect against malicious, deceptive, fraudulent, or illegal activity, or prosecute those responsible for such activities.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Debug products to identify and repair errors that impair existing intended functionality.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Exercise free speech, ensure the right of another consumer to exercise their free speech rights, or exercise another right provided for by law.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Comply with the California Electronic Communications Privacy Act (Cal. Penal Code § 1546 et. seq.).</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>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.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Enable solely internal uses that are reasonably aligned with consumer expectations based on your relationship with us.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Comply with a legal obligation.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Make other internal and lawful uses of that information that are compatible with the context in which you provided it.</Text>
</li>
</ol>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Exercising Your Rights to Know or Delete</Text>
<Text tagName='p' className={_s.mt15} size='medium'>To exercise your rights to know or delete described above, please submit a request by (preferred) e-mailing us at&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:dataprivacy@gab.com'
>
dataprivacy@gab.com
</Button>
&nbsp;; sending a request to us via our CCPA portal at&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='/about/ccpa/contact'
>
gab.com/about/ccpa/contact
</Button>
, or or mailing us at
</Text>
<Text tagName='p' className={_s.mt15} size='medium'>
Gab AI Inc.<br />
700 N State Street<br />
Clarks Summit, PA 18411<br />
United States of America
</Text>
<Text tagName='p' className={_s.mt15}size='medium'>Only you, or someone legally authorized to act on your behalf, may make a request to know or delete related to your personal information.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>You may only submit a request to know twice within a 12-month period. Your request to know or delete must:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>Provide sufficient information that allows us to reasonably verify you are the person about whom we collected personal information or an authorized representative.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Describe your request with sufficient detail that allows us to properly understand, evaluate, and respond to it.</Text>
</li>
</ul>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>We will only use personal information provided in the request to verify the requestor's identity or authority to make it.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>For instructions on exercising your sale opt-out or opt-in rights, see Personal Information Sales Opt-Out and Opt-In Rights.</Text>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Response Timing and Format</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:dataprivacy@gab.com'
>
dataprivacy@gab.com
</Button>
.
</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium' weight='bold'>Personal Information Sales Opt-Out and Opt-In Rights</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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.</Text>
<br />
<Heading size='h2'>Non-Discrimination</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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:</Text>
<ul className={[_s.d, _s.px15, _s.mt15, _s.ml15].join(' ')}>
<li>
<Text tagName='p' size='medium'>Deny you goods or services.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Charge you different prices or rates for goods or services, including through granting discounts or other benefits, or imposing penalties.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Provide you a different level or quality of goods or services.</Text>
</li>
<li className={_s.mt10}>
<Text tagName='p' size='medium'>Suggest that you may receive a different price or rate for goods or services or a different level or quality of goods or services.</Text>
</li>
</ul>
<br />
<Heading size='h2'>CCPA Rights Request Met</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>Metrics regarding the consumer rights requests we received from California residents from January 1, 2019 to December 31, 2019 appear in the following chart:</Text>
<Table
id='table-3'
columns={['Request Type', 'Received', 'Granted (in whole or in part)', 'Denied', 'Mean Days to Respond']}
rows={[
["Requests to Know", "0", "0", "0", "N/A"],
["Requests to Delete", "0", "0", "0", "N/A"],
["Requests to Opt-Out of Personal Information Sales", "N/A", "N/A", "N/A", "N/A"],
]}
/>
<br />
<Heading size='h2'>Other California Privacy Rights</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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.</Text>
<br />
<Heading size='h2'>Changes to Our Privacy Policy</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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.</Text>
<br />
<Heading size='h2'>Contact Information</Heading>
<Text tagName='p' className={[_s.mt15, _s.mb15].join(' ')} size='medium'>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:</Text>
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>
<Text weight='bold'>Contact form: </Text>
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:
</Text>
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>
<Text weight='bold'>Email: </Text>
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:dataprivacy@gab.com'
>
dataprivacy@gab.com
</Button>
</Text>
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>
<Text weight='bold'>Postal Address: </Text>
Gab AI Inc.<br />
700 N State Street<br />
Clarks Summit, PA 18411<br />
Attn: Data Privacy Department
</Text>
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>If you need to access this Policy in an alternative format due to having a disability, please contact&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:support@gab.com'
>
support@gab.com
</Button>
</Text>
</div>
</Block>
</div>
)
}
}

View File

@ -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 (
<div className={[_s.d].join(' ')}>
<Block>
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
<Heading>GAB AI INC</Heading>
<br />
<Heading>Contact form for Data Privacy</Heading>
<br />
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>
<Text weight='bold'>Email: </Text>
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:dataprivacy@gab.com'
>
dataprivacy@gab.com
</Button>
</Text>
</div>
</Block>
</div>
)
}
}

View File

@ -278,6 +278,40 @@ export default class PrivacyPolicy extends React.PureComponent {
<Heading size='h2'>Changes to Our Privacy Policy</Heading>
<Text tagName='p' className={_s.mt15} size='medium'>It 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.</Text>
<br />
<Heading size='h2'>Your California Privacy Rights</Heading>
<Text tagName='p' className={_s.mt15} size='medium'>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&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='/about/ccpa'
>
https://gab.com/about/ccpa
</Button>
</Text>
<Text tagName='p' className={_s.mt15} size='medium'>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&nbsp;
<Button
isText
underlineOnHover
color='brand'
backgroundColor='none'
className={_s.displayInline}
href='mailto:support@gab.com'
>
support@gab.com
</Button>
&nbsp;or write to us at
</Text>
<Text tagName='p' className={[_s.mt15].join(' ')} size='medium'>
Gab AI Inc.<br />
700 N State Street<br />
Clarks Summit, PA 18411<br />
Attn: Data Privacy Department
</Text>
<br />
<Heading size='h2'>Contact Information</Heading>
<Text tagName='p' className={_s.mt15} size='medium'>To ask questions or comment about this privacy policy and our privacy practices, contact us at:</Text>

View File

@ -57,6 +57,8 @@ import {
Assets,
BlockedAccounts,
BookmarkedStatuses,
CaliforniaConsumerProtection,
CaliforniaConsumerProtectionContact,
ChatConversationCreate,
ChatConversationRequests,
ChatConversationBlockedAccounts,
@ -199,6 +201,8 @@ class SwitchingArea extends React.PureComponent {
<WrappedRoute path='/about/privacy' publicRoute exact page={AboutPage} component={PrivacyPolicy} content={children} componentParams={{ title: 'Privacy Policy' }} />
<WrappedRoute path='/about/sales' publicRoute exact page={AboutPage} component={TermsOfSale} content={children} componentParams={{ title: 'Terms of Sale' }} />
<WrappedRoute path='/about/tos' publicRoute exact page={AboutPage} component={TermsOfService} content={children} componentParams={{ title: 'Terms of Service' }} />
<WrappedRoute path='/about/ccpa' publicRoute exact page={AboutPage} component={CaliforniaConsumerProtection} content={children} componentParams={{ title: 'Terms of Service' }} />
<WrappedRoute path='/about/ccpa/contact' publicRoute exact page={AboutPage} component={CaliforniaConsumerProtectionContact} content={children} componentParams={{ title: 'Terms of Service' }} />
<WrappedRoute path='/explore' publicRoute page={ExplorePage} component={ExploreTimeline} content={children} componentParams={{ title: 'Explore' }} />
<WrappedRoute path='/suggestions' exact page={BasicPage} component={Suggestions} content={children} componentParams={{ title: 'Suggestions' }} />

View File

@ -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 ChatConversationBlockedAccounts() { return import(/* webpackChunkName: "features/chat_conversation_blocked_accounts" */'../../chat_conversation_blocked_accounts') }
export function ChatConversationCreate() { return import(/* webpackChunkName: "features/chat_conversation_create" */'../../chat_conversation_create') }
export function ChatConversationCreateModal() { return import(/* webpackChunkName: "components/chat_conversation_create_modal" */'../../../components/modal/chat_conversation_create_modal') }

View File

@ -53,6 +53,10 @@ class AboutLayout extends React.PureComponent {
title: 'Terms of Service',
to: '/about/tos',
},
{
title: 'California Consumer Protection',
to: '/about/ccpa',
},
]
}

View File

@ -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('@')

View File

@ -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

26
app/models/link_block.rb Normal file
View File

@ -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

View File

@ -290,13 +290,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

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class LinkBlockPolicy < ApplicationPolicy
def index?
admin?
end
def create?
admin?
end
def destroy?
admin?
end
end

View File

@ -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

View File

@ -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)
ExpiringStatusWorker.perform_at(@status.expires_at, @status.id) if @status.expires_at && @account.is_pro
PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
@ -118,12 +119,17 @@ 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?)
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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -31,6 +31,7 @@ SimpleNavigation::Configuration.run do |navigation|
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
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|

View File

@ -100,6 +100,7 @@ Rails.application.routes.draw do
get '/dashboard', to: 'dashboard#index'
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]

View File

@ -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

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_12_03_214600) 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"
@ -394,6 +394,13 @@ ActiveRecord::Schema.define(version: 2020_12_03_214600) do
t.index ["user_id"], name: "index_identities_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