diff --git a/conf/configuration.go b/conf/configuration.go index 44bb10d7f..e2a467e7a 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -26,6 +26,7 @@ type configOptions struct { BaseURL string UILoginBackgroundURL string EnableTranscodingConfig bool + EnableDownloads bool TranscodingCacheSize string ImageCacheSize string AutoImportPlaylists bool @@ -113,6 +114,7 @@ func init() { viper.SetDefault("transcodingcachesize", "100MB") viper.SetDefault("imagecachesize", "100MB") viper.SetDefault("autoimportplaylists", true) + viper.SetDefault("enabledownloads", true) // Config options only valid for file/env configuration viper.SetDefault("searchfullstring", false) diff --git a/server/app/serve_index.go b/server/app/serve_index.go index 1cce76bfd..a143a5b2b 100644 --- a/server/app/serve_index.go +++ b/server/app/serve_index.go @@ -34,6 +34,7 @@ func serveIndex(ds model.DataStore, fs http.FileSystem) http.HandlerFunc { "welcomeMessage": policy.Sanitize(conf.Server.UIWelcomeMessage), "enableTranscodingConfig": conf.Server.EnableTranscodingConfig, "gaTrackingId": conf.Server.GATrackingID, + "enableDownloads": conf.Server.EnableDownloads, "devActivityMenu": conf.Server.DevActivityMenu, } j, err := json.Marshal(appConfig) diff --git a/server/app/serve_index_test.go b/server/app/serve_index_test.go index bf0621c44..8c9b0d3ee 100644 --- a/server/app/serve_index_test.go +++ b/server/app/serve_index_test.go @@ -103,6 +103,17 @@ var _ = Describe("serveIndex", func() { Expect(config).To(HaveKeyWithValue("enableTranscodingConfig", true)) }) + It("sets the enableDownloads", func() { + conf.Server.EnableDownloads = true + r := httptest.NewRequest("GET", "/index.html", nil) + w := httptest.NewRecorder() + + serveIndex(ds, fs)(w, r) + + config := extractAppConfig(w.Body.String()) + Expect(config).To(HaveKeyWithValue("enableDownloads", true)) + }) + It("sets the gaTrackingId", func() { conf.Server.GATrackingID = "UA-12345" r := httptest.NewRequest("GET", "/index.html", nil) diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index d6131ca33..33bf12866 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -8,9 +8,11 @@ import ( "strconv" "strings" + "github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/core" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" + "github.com/deluan/navidrome/model/request" "github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/utils" ) @@ -80,11 +82,17 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() + username, _ := request.UsernameFrom(ctx) id, err := requiredParamString(r, "id") if err != nil { return nil, err } + if !conf.Server.EnableDownloads { + log.Warn(ctx, "Downloads are disabled", "user", username, "id", id) + return nil, newError(responses.ErrorAuthorizationFail, "downloads are disabled") + } + entity, err := core.GetEntityByID(ctx, c.ds, id) if err != nil { return nil, err diff --git a/ui/src/album/AlbumActions.js b/ui/src/album/AlbumActions.js index 52f482008..b13284758 100644 --- a/ui/src/album/AlbumActions.js +++ b/ui/src/album/AlbumActions.js @@ -15,6 +15,7 @@ import { playNext, addTracks, playTracks, shuffleTracks } from '../actions' import subsonic from '../subsonic' import { formatBytes } from '../common/SizeField' import { useMediaQuery } from '@material-ui/core' +import config from '../config' const AlbumActions = ({ className, ids, data, record, ...rest }) => { const dispatch = useDispatch() @@ -67,15 +68,17 @@ const AlbumActions = ({ className, ids, data, record, ...rest }) => { > - + {config.enableDownloads && ( + + )} ) } diff --git a/ui/src/common/ContextMenus.js b/ui/src/common/ContextMenus.js index 3082ab71b..88f8d36e7 100644 --- a/ui/src/common/ContextMenus.js +++ b/ui/src/common/ContextMenus.js @@ -17,6 +17,7 @@ import { import subsonic from '../subsonic' import StarButton from './StarButton' import { formatBytes } from './SizeField' +import config from '../config' const useStyles = makeStyles({ noWrap: { @@ -45,31 +46,37 @@ const ContextMenu = ({ const options = { play: { + enabled: true, needData: true, label: translate('resources.album.actions.playAll'), action: (data, ids) => dispatch(playTracks(data, ids)), }, playNext: { + enabled: true, needData: true, label: translate('resources.album.actions.playNext'), action: (data, ids) => dispatch(playNext(data, ids)), }, addToQueue: { + enabled: true, needData: true, label: translate('resources.album.actions.addToQueue'), action: (data, ids) => dispatch(addTracks(data, ids)), }, shuffle: { + enabled: true, needData: true, label: translate('resources.album.actions.shuffle'), action: (data, ids) => dispatch(shuffleTracks(data, ids)), }, addToPlaylist: { + enabled: true, needData: true, label: translate('resources.album.actions.addToPlaylist'), action: (data, ids) => dispatch(openAddToPlaylist({ selectedIds: ids })), }, download: { + enabled: config.enableDownloads, needData: false, label: `${translate('resources.album.actions.download')} (${formatBytes( record.size @@ -146,11 +153,14 @@ const ContextMenu = ({ open={open} onClose={handleOnClose} > - {Object.keys(options).map((key) => ( - - {options[key].label} - - ))} + {Object.keys(options).map( + (key) => + options[key].enabled && ( + + {options[key].label} + + ) + )} ) diff --git a/ui/src/common/SongContextMenu.js b/ui/src/common/SongContextMenu.js index 133ef4e50..a8e603162 100644 --- a/ui/src/common/SongContextMenu.js +++ b/ui/src/common/SongContextMenu.js @@ -9,6 +9,7 @@ import { playNext, addTracks, setTrack, openAddToPlaylist } from '../actions' import subsonic from '../subsonic' import StarButton from './StarButton' import { formatBytes } from './SizeField' +import config from '../config' const useStyles = makeStyles({ noWrap: { @@ -32,18 +33,22 @@ const SongContextMenu = ({ const [anchorEl, setAnchorEl] = useState(null) const options = { playNow: { + enabled: true, label: translate('resources.song.actions.playNow'), action: (record) => dispatch(setTrack(record)), }, playNext: { + enabled: true, label: translate('resources.song.actions.playNext'), action: (record) => dispatch(playNext({ [record.id]: record })), }, addToQueue: { + enabled: true, label: translate('resources.song.actions.addToQueue'), action: (record) => dispatch(addTracks({ [record.id]: record })), }, addToPlaylist: { + enabled: true, label: translate('resources.song.actions.addToPlaylist'), action: (record) => dispatch( @@ -54,6 +59,7 @@ const SongContextMenu = ({ ), }, download: { + enabled: config.enableDownloads, label: `${translate('resources.song.actions.download')} (${formatBytes( record.size )})`, @@ -95,11 +101,14 @@ const SongContextMenu = ({ open={open} onClose={handleClose} > - {Object.keys(options).map((key) => ( - - {options[key].label} - - ))} + {Object.keys(options).map( + (key) => + options[key].enabled && ( + + {options[key].label} + + ) + )} ) diff --git a/ui/src/config.js b/ui/src/config.js index 8277cd3ce..fcb4de3c2 100644 --- a/ui/src/config.js +++ b/ui/src/config.js @@ -7,6 +7,7 @@ const defaultConfig = { baseURL: '', loginBackgroundURL: 'https://source.unsplash.com/random/1600x900?music', enableTranscodingConfig: true, + enableDownloads: true, welcomeMessage: '', gaTrackingId: '', devActivityMenu: true, diff --git a/ui/src/playlist/PlaylistActions.js b/ui/src/playlist/PlaylistActions.js index e8dc3d893..0586514e9 100644 --- a/ui/src/playlist/PlaylistActions.js +++ b/ui/src/playlist/PlaylistActions.js @@ -18,6 +18,7 @@ import subsonic from '../subsonic' import PropTypes from 'prop-types' import { formatBytes } from '../common/SizeField' import { useMediaQuery } from '@material-ui/core' +import config from '../config' const PlaylistActions = ({ className, ids, data, record, ...rest }) => { const dispatch = useDispatch() @@ -88,15 +89,17 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => { > - + {config.enableDownloads && ( + + )}