From cef94e5743fb57b87b7ae7158bafc90b5742aa16 Mon Sep 17 00:00:00 2001 From: jessop Date: Mon, 7 Jan 2019 17:44:34 -0500 Subject: [PATCH] enhances dmca system --- cli/defaults/siteConfig.json | 3 +- client/scss/_asset-preview.scss | 10 +++ client/src/components/AssetPreview/index.jsx | 64 +++++++++++--------- client/src/containers/AssetBlocked/index.js | 13 ++++ client/src/containers/AssetBlocked/view.jsx | 58 ++++++++++++++++++ client/src/pages/ShowAssetDetails/view.jsx | 44 ++++++++------ server/index.js | 39 +++++++++--- server/models/blocked.js | 18 ++++-- server/utils/blockList.js | 26 ++++++++ server/utils/getClaimData.js | 9 ++- 10 files changed, 224 insertions(+), 60 deletions(-) create mode 100644 client/src/containers/AssetBlocked/index.js create mode 100644 client/src/containers/AssetBlocked/view.jsx create mode 100644 server/utils/blockList.js diff --git a/cli/defaults/siteConfig.json b/cli/defaults/siteConfig.json index 3b8395c4..de2eca7b 100644 --- a/cli/defaults/siteConfig.json +++ b/cli/defaults/siteConfig.json @@ -17,7 +17,8 @@ "ipAddress": "", "host": "https://www.example.com", "description": "A decentralized hosting platform built on LBRY", - "twitter": false + "twitter": false, + "blockListEndpoint": "https://api.lbry.io/file/list_blocked" }, "publishing": { "primaryClaimAddress": null, diff --git a/client/scss/_asset-preview.scss b/client/scss/_asset-preview.scss index a05c2a2c..02a02ae5 100644 --- a/client/scss/_asset-preview.scss +++ b/client/scss/_asset-preview.scss @@ -2,6 +2,16 @@ position: relative; } +.asset-preview__blocked { + box-sizing: border-box; + background: black; + color: white; + height: 80%; + padding: 5px; + //remove margin-bottom after mystery 5px on wrapper is gone. + margin-bottom: 5px; +} + .asset-preview__image { width : 100%; padding: 0; diff --git a/client/src/components/AssetPreview/index.jsx b/client/src/components/AssetPreview/index.jsx index 5599709e..2d452b36 100644 --- a/client/src/components/AssetPreview/index.jsx +++ b/client/src/components/AssetPreview/index.jsx @@ -2,51 +2,59 @@ import React from 'react'; import { Link } from 'react-router-dom'; import createCanonicalLink from '../../../../utils/createCanonicalLink'; -const ClaimPending = () => { - return ( -
PENDING
- ); -}; - const AssetPreview = ({ defaultThumbnail, claimData }) => { - const {name, fileExt, contentType, thumbnail, title, pending} = claimData; + const {name, fileExt, contentType, thumbnail, title, blocked} = claimData; const showUrl = createCanonicalLink({asset: {...claimData}}); const embedUrl = `${showUrl}.${fileExt}`; - switch (contentType) { - case 'image/jpeg': - case 'image/jpg': - case 'image/png': - case 'image/gif': - return ( - -
+ + /* + This blocked section shouldn't be necessary after pagination is reworked, + though it might be useful for channel_mine situations. + */ + + if (blocked) { + return ( +
+
+

Error 451

+

This content is blocked for legal reasons.

+
+

Blocked Content

+
+ ); + } else { + switch (contentType) { + case 'image/jpeg': + case 'image/jpg': + case 'image/png': + case 'image/gif': + return ( + {name}

{title}

-
- - ); - case 'video/mp4': - return ( - -
+ + ); + case 'video/mp4': + return ( +
{name} -
+

{title}

-
- - ); - default: - return null; + + ); + default: + return null; + } } }; diff --git a/client/src/containers/AssetBlocked/index.js b/client/src/containers/AssetBlocked/index.js new file mode 100644 index 00000000..933762d8 --- /dev/null +++ b/client/src/containers/AssetBlocked/index.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import View from './view'; +import { selectAsset } from '../../selectors/show'; + +const mapStateToProps = (props) => { + const {show} = props; + const asset = selectAsset(show); + return { + asset, + }; +}; + +export default connect(mapStateToProps, null)(View); diff --git a/client/src/containers/AssetBlocked/view.jsx b/client/src/containers/AssetBlocked/view.jsx new file mode 100644 index 00000000..de79a14a --- /dev/null +++ b/client/src/containers/AssetBlocked/view.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import createCanonicalLink from '../../../../utils/createCanonicalLink'; +import HorizontalSplit from '@components/HorizontalSplit'; +/* +This component shouldn't be necessary after pagination is reworked, +though it might be useful for channel_mine situations. +*/ +class BlockedLeft extends React.PureComponent { + render () { + return ( + + {'451 + + ); + } +} + +class BlockedRight extends React.PureComponent { + render () { + return ( + +

In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.

+

Click here for more information.

+
+ ); + } +} + +class AssetBlocked extends React.Component { + componentDidMount () { + /* + This function and fetch exists to send the browser the appropriate 451 error. + */ + const { asset } = this.props; + const { claimData: { contentType, outpoint } } = asset; + let fileExt; + if (typeof contentType === 'string') { + fileExt = contentType.split('/')[1] || 'jpg'; + } + const sourceUrl = `${createCanonicalLink({ asset: asset.claimData })}.${fileExt}?${outpoint}`; + fetch(sourceUrl) + .catch(); + } + + render () { + return ( +
+ } + rightSide={} + /> +
+ ); + } +}; + +export default AssetBlocked; diff --git a/client/src/pages/ShowAssetDetails/view.jsx b/client/src/pages/ShowAssetDetails/view.jsx index 7798531d..71051454 100644 --- a/client/src/pages/ShowAssetDetails/view.jsx +++ b/client/src/pages/ShowAssetDetails/view.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PageLayout from '@components/PageLayout'; import * as Icon from 'react-feather'; import AssetDisplay from '@containers/AssetDisplay'; +import AssetBlocked from '@containers/AssetBlocked'; import AssetInfo from '@containers/AssetInfo'; import ErrorPage from '@pages/ErrorPage'; import AssetTitle from '@containers/AssetTitle'; @@ -29,23 +30,32 @@ class ShowAssetDetails extends React.Component { render () { const { asset } = this.props; if (asset) { - const { claimData: { name } } = asset; - return ( - -
- - - - -
- {!this.state.closed && } -
- ); + const { claimData: { name, blocked } } = asset; + if (!blocked) { + return ( + +
+ + + +
+ {!this.state.closed && } +
+ ); + } else { + return ( + +
+ +
+
+ ); + } } return ( diff --git a/server/index.js b/server/index.js index 4d32f4b4..0ec835cf 100644 --- a/server/index.js +++ b/server/index.js @@ -16,6 +16,7 @@ const createDatabaseIfNotExists = require('./models/utils/createDatabaseIfNotExi const { getWalletBalance } = require('./lbrynet/index'); const configureLogging = require('./utils/configureLogging'); const configureSlack = require('./utils/configureSlack'); +const { setupBlockList } = require('./utils/blockList'); const speechPassport = require('./speechPassport'); const processTrending = require('./utils/processTrending'); @@ -25,7 +26,7 @@ const { } = require('./middleware/logMetricsMiddleware'); const { - details: { port: PORT }, + details: { port: PORT, blockListEndpoint }, startup: { performChecks, performUpdates, @@ -34,6 +35,9 @@ const { const { sessionKey } = require('@private/authConfig.json'); +// configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break. +let bLE; + function Server () { this.initialize = () => { // configure logging @@ -166,19 +170,37 @@ function Server () { logger.info('Starting LBC balance:', walletBalance); }); }; + this.performUpdates = () => { if (!performUpdates) { return; } + if (blockListEndpoint) { + bLE = blockListEndpoint; + } else if (!blockListEndpoint) { + if (typeof (blockListEndpoint) !== 'string') { + logger.warn('blockListEndpoint is null due to outdated siteConfig file. \n' + + 'Continuing with default LBRY blocklist api endpoint. \n ' + + '(Specify /"blockListEndpoint" : ""/ to disable.') + bLE = 'https://api.lbry.io/file/list_blocked'; + } + } logger.info(`Peforming updates...`); - return Promise.all([ - db.Blocked.refreshTable(), - db.Tor.refreshTable(), - ]) - .then(([updatedBlockedList, updatedTorList]) => { - logger.info('Blocked list updated, length:', updatedBlockedList.length); + if (!bLE) { + logger.info('Configured for no Block List') + db.Tor.refreshTable().then( (updatedTorList) => { logger.info('Tor list updated, length:', updatedTorList.length); }); + } else { + + return Promise.all([ + db.Blocked.refreshTable(bLE), + db.Tor.refreshTable()]) + .then(([updatedBlockedList, updatedTorList]) => { + logger.info('Blocked list updated, length:', updatedBlockedList.length); + logger.info('Tor list updated, length:', updatedTorList.length); + }) + } }; this.start = () => { this.initialize(); @@ -194,6 +216,9 @@ function Server () { this.performUpdates(), ]); }) + .then(() => { + return setupBlockList(); + }) .then(() => { logger.info('Spee.ch startup is complete'); diff --git a/server/models/blocked.js b/server/models/blocked.js index 5e413889..fff43d51 100644 --- a/server/models/blocked.js +++ b/server/models/blocked.js @@ -1,5 +1,4 @@ const logger = require('winston'); - const BLOCKED_CLAIM = 'BLOCKED_CLAIM'; module.exports = (sequelize, { STRING }) => { @@ -16,6 +15,14 @@ module.exports = (sequelize, { STRING }) => { } ); + Blocked.getBlockList = function () { + logger.debug('returning full block list'); + return new Promise((resolve, reject) => { + this.findAll() + .then(list => { return resolve(list) }); + }); + }; + Blocked.isNotBlocked = function (outpoint) { logger.debug(`checking to see if ${outpoint} is not blocked`); return new Promise((resolve, reject) => { @@ -37,9 +44,10 @@ module.exports = (sequelize, { STRING }) => { }); }; - Blocked.refreshTable = function () { + Blocked.refreshTable = function (blockEndpoint) { let blockedList = []; - return fetch('https://api.lbry.io/file/list_blocked') + + return fetch(blockEndpoint) .then(response => { return response.json(); }) @@ -50,9 +58,7 @@ module.exports = (sequelize, { STRING }) => { if (!jsonResponse.data.outpoints) { throw new Error('no outpoints in list_blocked response'); } - return jsonResponse.data.outpoints; - }) - .then(outpoints => { + let outpoints = jsonResponse.data.outpoints; logger.debug('total outpoints:', outpoints.length); // prep the records for (let i = 0; i < outpoints.length; i++) { diff --git a/server/utils/blockList.js b/server/utils/blockList.js new file mode 100644 index 00000000..0c7637c2 --- /dev/null +++ b/server/utils/blockList.js @@ -0,0 +1,26 @@ +const logger = require('winston'); +const db = require('../models'); + +let blockList = new Set(); + +const setupBlockList = (intervalInSeconds = 60) => { + const fetchList = () => { + return new Promise((resolve, reject) => { + db.Blocked.getBlockList() + .then((result) => { + blockList.clear(); + if (result.length > 0) { + result.map((item) => { blockList.add(item.dataValues.outpoint) }); + resolve(); + } else reject(); + }) + .catch(e => { console.error('list was empty', e) }); + }); + }; + setInterval(() => { fetchList() }, intervalInSeconds * 1000); + return fetchList(); +}; +module.exports = { + isBlocked: (outpoint) => { return blockList.has(outpoint) }, + setupBlockList, +}; diff --git a/server/utils/getClaimData.js b/server/utils/getClaimData.js index b898d683..86385a96 100644 --- a/server/utils/getClaimData.js +++ b/server/utils/getClaimData.js @@ -1,6 +1,7 @@ const { details: { host } } = require('@config/siteConfig'); const chainquery = require('chainquery').default; const { getClaim } = require('server/lbrynet'); +const { isBlocked } = require('./blockList'); module.exports = async (data, chName = null, chShortId = null) => { // TODO: Refactor getching the channel name out; requires invasive changes. @@ -9,6 +10,11 @@ module.exports = async (data, chName = null, chShortId = null) => { let lbrynetFileExt = null; let channelShortId = chShortId; let channelName = chName; + let blocked; + const outPoint = `${data.transaction_hash_id}:${data.vout}`; + if (isBlocked(outPoint)) { + blocked = true; + } if (!chName && certificateId && !channelName) { channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(() => { @@ -38,8 +44,9 @@ module.exports = async (data, chName = null, chShortId = null) => { fileExt : data.generated_extension || data.fileExt || lbrynetFileExt, description: data.description, thumbnail : data.generated_thumbnail || data.thumbnail_url || data.thumbnail, - outpoint : `${data.transaction_hash_id}:${data.vout}` || data.outpoint, + outpoint : outPoint || data.outpoint, host, pending : Boolean(data.height === 0), + blocked : blocked, }); };