Explorar el Código

enable user device access

Luke Pulverenti hace 10 años
padre
commit
8a9f16ff6a
Se han modificado 26 ficheros con 223 adiciones y 100 borrados
  1. 20 2
      MediaBrowser.Api/Session/SessionsService.cs
  2. 4 3
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  3. 0 1
      MediaBrowser.Common.Implementations/packages.config
  4. 8 0
      MediaBrowser.Controller/Devices/IDeviceManager.cs
  5. 15 0
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  6. 16 1
      MediaBrowser.Controller/Entities/Video.cs
  7. 12 4
      MediaBrowser.Model/Net/MimeTypes.cs
  8. 6 0
      MediaBrowser.Model/Users/UserPolicy.cs
  9. 3 2
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  10. 15 8
      MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
  11. 7 0
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  12. 1 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  13. 37 0
      MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
  14. 41 19
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
  15. 0 5
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
  16. 13 44
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  17. 4 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  18. 4 0
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  19. 1 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  20. 8 0
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  21. 1 1
      MediaBrowser.Server.Implementations/packages.config
  22. 1 1
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  23. 2 3
      Nuget/MediaBrowser.Common.Internal.nuspec
  24. 1 1
      Nuget/MediaBrowser.Common.nuspec
  25. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  26. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 20 - 2
MediaBrowser.Api/Session/SessionsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
@@ -299,6 +300,7 @@ namespace MediaBrowser.Api.Session
         private readonly IUserManager _userManager;
         private readonly IAuthorizationContext _authContext;
         private readonly IAuthenticationRepository _authRepo;
+        private readonly IDeviceManager _deviceManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SessionsService" /> class.
@@ -307,12 +309,13 @@ namespace MediaBrowser.Api.Session
         /// <param name="userManager">The user manager.</param>
         /// <param name="authContext">The authentication context.</param>
         /// <param name="authRepo">The authentication repo.</param>
-        public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo)
+        public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
         {
             _sessionManager = sessionManager;
             _userManager = userManager;
             _authContext = authContext;
             _authRepo = authRepo;
+            _deviceManager = deviceManager;
         }
 
         public void Delete(RevokeKey request)
@@ -382,6 +385,21 @@ namespace MediaBrowser.Api.Session
                 {
                     result = result.Where(i => !i.UserId.HasValue);
                 }
+
+                result = result.Where(i =>
+                {
+                    var deviceId = i.DeviceId;
+
+                    if (!string.IsNullOrWhiteSpace(deviceId))
+                    {
+                        if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), deviceId))
+                        {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                });
             }
 
             return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());

+ 4 - 3
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -52,6 +52,10 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath>
     </Reference>
+    <Reference Include="SharpCompress, Version=0.10.2.0, Culture=neutral, PublicKeyToken=beaf6f427e128133, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
+    </Reference>
     <Reference Include="SimpleInjector, Version=2.6.1.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\SimpleInjector.2.6.1\lib\net45\SimpleInjector.dll</HintPath>
@@ -65,9 +69,6 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Net" />
     <Reference Include="System.Xml" />
-    <Reference Include="SharpCompress">
-      <HintPath>..\packages\sharpcompress.0.10.2\lib\net40\SharpCompress.dll</HintPath>
-    </Reference>
     <Reference Include="ServiceStack.Text">
       <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
     </Reference>

+ 0 - 1
MediaBrowser.Common.Implementations/packages.config

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="NLog" version="3.1.0.0" targetFramework="net45" />
-  <package id="SharpCompress" version="0.10.2" targetFramework="net45" />
   <package id="SimpleInjector" version="2.6.1" targetFramework="net45" />
 </packages>

+ 8 - 0
MediaBrowser.Controller/Devices/IDeviceManager.cs

@@ -85,5 +85,13 @@ namespace MediaBrowser.Controller.Devices
         /// <param name="file">The file.</param>
         /// <returns>Task.</returns>
         Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file);
+
+        /// <summary>
+        /// Determines whether this instance [can access device] the specified user identifier.
+        /// </summary>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="deviceId">The device identifier.</param>
+        /// <returns><c>true</c> if this instance [can access device] the specified user identifier; otherwise, <c>false</c>.</returns>
+        bool CanAccessDevice(string userId, string deviceId);
     }
 }

+ 15 - 0
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -89,6 +89,21 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
         }
 
+        [IgnoreDataMember]
+        public bool IsArchive
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(Path))
+                {
+                    return false;
+                }
+                var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+                return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+            }
+        }
+
         /// <summary>
         /// Gets or sets the artist.
         /// </summary>

+ 16 - 1
MediaBrowser.Controller/Entities/Video.cs

@@ -91,6 +91,21 @@ namespace MediaBrowser.Controller.Entities
             get { return LocalAlternateVersions.Count > 0; }
         }
 
+        [IgnoreDataMember]
+        public bool IsArchive
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(Path))
+                {
+                    return false;
+                }
+                var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+                return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+            }
+        }
+
         public IEnumerable<Guid> GetAdditionalPartIds()
         {
             return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
@@ -246,7 +261,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                         return System.IO.Path.GetFileName(Path);
                     }
-                    
+
                     return System.IO.Path.GetFileNameWithoutExtension(Path);
                 }
 

+ 12 - 4
MediaBrowser.Model/Net/MimeTypes.cs

@@ -73,10 +73,18 @@ namespace MediaBrowser.Model.Net
                 {".m4v", "video/x-m4v"}
             };
 
-        private static readonly Dictionary<string, string> ExtensionLookup =
-           MimeTypeLookup
-           .GroupBy(i => i.Value)
-           .ToDictionary(x => x.Key, x => x.First().Key, StringComparer.OrdinalIgnoreCase);
+        private static readonly Dictionary<string, string> ExtensionLookup = CreateExtensionLookup();
+
+        private static Dictionary<string, string> CreateExtensionLookup()
+        {
+            var dict = MimeTypeLookup
+                .GroupBy(i => i.Value)
+                .ToDictionary(x => x.Key, x => x.First().Key, StringComparer.OrdinalIgnoreCase);
+
+            dict["image/jpg"] = ".jpg";
+
+            return dict;
+        }
 
         /// <summary>
         /// Gets the type of the MIME.

+ 6 - 0
MediaBrowser.Model/Users/UserPolicy.cs

@@ -49,6 +49,9 @@ namespace MediaBrowser.Model.Users
         /// <value><c>true</c> if [enable synchronize]; otherwise, <c>false</c>.</value>
         public bool EnableSync { get; set; }
 
+        public string[] EnabledDevices { get; set; }
+        public bool EnableAllDevices { get; set; }
+
         public UserPolicy()
         {
             EnableLiveTvManagement = true;
@@ -64,6 +67,9 @@ namespace MediaBrowser.Model.Users
             EnableUserPreferenceAccess = true;
 
             AccessSchedules = new AccessSchedule[] { };
+
+            EnabledDevices = new string[] { };
+            EnableAllDevices = true;
         }
     }
 }

+ 3 - 2
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
@@ -132,7 +131,9 @@ namespace MediaBrowser.Providers.MediaInfo
 
         public bool Supports(IHasImages item)
         {
-            return item is Audio;
+            var audio = item as Audio;
+
+            return item.LocationType == LocationType.FileSystem && audio != null && !audio.IsArchive;
         }
 
         public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)

+ 15 - 8
MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs

@@ -38,6 +38,13 @@ namespace MediaBrowser.Providers.MediaInfo
         public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
             where T : Audio
         {
+            if (item.IsArchive)
+            {
+                var ext = Path.GetExtension(item.Path) ?? string.Empty;
+                item.Container = ext.TrimStart('.');
+                return ItemUpdateType.MetadataImport;
+            }
+
             var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 
             cancellationToken.ThrowIfCancellationRequested();
@@ -58,8 +65,8 @@ namespace MediaBrowser.Providers.MediaInfo
             cancellationToken.ThrowIfCancellationRequested();
 
             var idString = item.Id.ToString("N");
-            var cachePath = Path.Combine(_appPaths.CachePath, 
-                "ffprobe-audio", 
+            var cachePath = Path.Combine(_appPaths.CachePath,
+                "ffprobe-audio",
                 idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json");
 
             try
@@ -132,7 +139,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 if (!string.IsNullOrEmpty(data.format.size))
                 {
-                    audio.Size = long.Parse(data.format.size , _usCulture);
+                    audio.Size = long.Parse(data.format.size, _usCulture);
                 }
                 else
                 {
@@ -217,9 +224,9 @@ namespace MediaBrowser.Providers.MediaInfo
             audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
 
             // Several different forms of retaildate
-            audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? 
-                FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? 
-                FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? 
+            audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
+                FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
+                FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
                 FFProbeHelpers.GetDictionaryDateTime(tags, "date");
 
             // If we don't have a ProductionYear try and get it from PremiereDate
@@ -261,8 +268,8 @@ namespace MediaBrowser.Providers.MediaInfo
         {
             // Only use the comma as a delimeter if there are no slashes or pipes. 
             // We want to be careful not to split names that have commas in them
-            var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? 
-                _nameDelimiters : 
+            var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
+                _nameDelimiters :
                 new[] { ',' };
 
             return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)

+ 7 - 0
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -71,6 +71,13 @@ namespace MediaBrowser.Providers.MediaInfo
             CancellationToken cancellationToken)
             where T : Video
         {
+            if (item.IsArchive)
+            {
+                var ext = Path.GetExtension(item.Path) ?? string.Empty;
+                item.Container = ext.TrimStart('.');
+                return ItemUpdateType.MetadataImport;
+            }
+
             var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
 
             BlurayDiscInfo blurayDiscInfo = null;

+ 1 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.MediaInfo
         {
             var video = item as Video;
 
-            return item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut;
+            return item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut && !video.IsArchive;
         }
 
         public int Order

+ 37 - 0
MediaBrowser.Server.Implementations/Devices/DeviceManager.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Session;
@@ -13,6 +14,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Server.Implementations.Devices
 {
@@ -188,6 +190,41 @@ namespace MediaBrowser.Server.Implementations.Devices
 
             EventHelper.FireEventIfNotNull(DeviceOptionsUpdated, this, new GenericEventArgs<DeviceInfo>(device), _logger);
         }
+
+        public bool CanAccessDevice(string userId, string deviceId)
+        {
+            if (string.IsNullOrWhiteSpace(userId))
+            {
+                throw new ArgumentNullException("userId");
+            }
+            if (string.IsNullOrWhiteSpace(deviceId))
+            {
+                throw new ArgumentNullException("deviceId");
+            }
+
+            var user = _userManager.GetUserById(userId);
+            if (!CanAccessDevice(user.Policy, deviceId))
+            {
+                var capabilities = GetCapabilities(deviceId);
+
+                if (capabilities.SupportsUniqueIdentifier)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private bool CanAccessDevice(UserPolicy policy, string id)
+        {
+            if (policy.EnableAllDevices)
+            {
+                return true;
+            }
+
+            return ListHelper.ContainsIgnoreCase(policy.EnabledDevices, id);
+        }
     }
 
     public class DevicesConfigStore : IConfigurationFactory

+ 41 - 19
MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
+using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
@@ -15,10 +16,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
     {
         private readonly IServerConfigurationManager _config;
 
-        public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager)
+        public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, IDeviceManager deviceManager)
         {
             AuthorizationContext = authorizationContext;
             _config = config;
+            DeviceManager = deviceManager;
             SessionManager = sessionManager;
             ConnectManager = connectManager;
             UserManager = userManager;
@@ -28,6 +30,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
         public IAuthorizationContext AuthorizationContext { get; private set; }
         public IConnectManager ConnectManager { get; private set; }
         public ISessionManager SessionManager { get; private set; }
+        public IDeviceManager DeviceManager { get; private set; }
 
         /// <summary>
         /// Redirect the client to a specific URL if authentication failed.
@@ -68,24 +71,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
 
             if (user != null)
             {
-                if (user.Policy.IsDisabled)
-                {
-                    throw new SecurityException("User account has been disabled.")
-                    {
-                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
-                    };
-                }
-
-                if (!user.Policy.IsAdministrator &&
-                    !authAttribtues.EscapeParentalControl &&
-                    !user.IsParentalScheduleAllowed())
-                {
-                    request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
-                    throw new SecurityException("This user account is not allowed access at this time.")
-                    {
-                        SecurityExceptionType = SecurityExceptionType.ParentalControl
-                    };
-                }
+                ValidateUserAccess(user, request, authAttribtues, auth);
             }
 
             if (!IsExemptFromRoles(auth, authAttribtues))
@@ -108,6 +94,42 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
             }
         }
 
+        private void ValidateUserAccess(User user, IServiceRequest request,
+            IAuthenticationAttributes authAttribtues,
+            AuthorizationInfo auth)
+        {
+            if (user.Policy.IsDisabled)
+            {
+                throw new SecurityException("User account has been disabled.")
+                {
+                    SecurityExceptionType = SecurityExceptionType.Unauthenticated
+                };
+            }
+
+            if (!user.Policy.IsAdministrator &&
+                !authAttribtues.EscapeParentalControl &&
+                !user.IsParentalScheduleAllowed())
+            {
+                request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
+
+                throw new SecurityException("This user account is not allowed access at this time.")
+                {
+                    SecurityExceptionType = SecurityExceptionType.ParentalControl
+                };
+            }
+
+            if (!string.IsNullOrWhiteSpace(auth.DeviceId))
+            {
+                if (!DeviceManager.CanAccessDevice(user.Id.ToString("N"), auth.DeviceId))
+                {
+                    throw new SecurityException("User is not allowed access from this device.")
+                    {
+                        SecurityExceptionType = SecurityExceptionType.ParentalControl
+                    };
+                }
+            }
+        }
+
         private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
         {
             if (!_config.Configuration.IsStartupWizardCompleted &&

+ 0 - 5
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs

@@ -25,11 +25,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
             // Contains [boxset] in the path
             if (args.IsDirectory)
             {
-                if (IsInvalid(args.GetCollectionType()))
-                {
-                    return null;
-                }
-                
                 var filename = Path.GetFileName(args.Path);
 
                 if (string.IsNullOrEmpty(filename))

+ 13 - 44
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -1,12 +1,9 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using MediaBrowser.Naming.Common;
 using MediaBrowser.Naming.IO;
 using MediaBrowser.Naming.Video;
@@ -22,16 +19,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
     /// </summary>
     public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
     {
-        private readonly IServerApplicationPaths _applicationPaths;
-        private readonly ILogger _logger;
-        private readonly IFileSystem _fileSystem;
-
-        public MovieResolver(ILibraryManager libraryManager, IServerApplicationPaths applicationPaths, ILogger logger, IFileSystem fileSystem)
-            : base(libraryManager)
+        public MovieResolver(ILibraryManager libraryManager) : base(libraryManager)
         {
-            _applicationPaths = applicationPaths;
-            _logger = logger;
-            _fileSystem = fileSystem;
         }
 
         /// <summary>
@@ -62,12 +51,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
 
             if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
             {
-                return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType);
+                return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType, false);
             }
 
             if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
             {
-                return ResolveVideos<Video>(parent, files, directoryService, collectionType);
+                return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
             }
 
             if (string.IsNullOrEmpty(collectionType))
@@ -75,21 +64,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                 // Owned items should just use the plain video type
                 if (parent == null)
                 {
-                    return ResolveVideos<Video>(parent, files, directoryService, collectionType);
+                    return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
                 }
 
-                return ResolveVideos<Video>(parent, files, directoryService, collectionType);
+                return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
             }
 
             if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
             {
-                return ResolveVideos<Movie>(parent, files, directoryService, collectionType);
+                return ResolveVideos<Movie>(parent, files, directoryService, collectionType, false);
             }
 
             return null;
         }
 
-        private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType)
+        private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool suppportMultiEditions)
             where T : Video, new()
         {
             var files = new List<FileSystemInfo>();
@@ -115,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                 FullName = i.FullName,
                 Type = FileInfoType.File
 
-            }).ToList(), false).ToList();
+            }).ToList(), suppportMultiEditions).ToList();
 
             var result = new MultiItemResolverResult
             {
@@ -135,7 +124,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                     IsInMixedFolder = isInMixedFolder,
                     ProductionYear = video.Year,
                     Name = video.Name,
-                    AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList()
+                    AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList(),
+                    LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToList()
                 };
 
                 SetVideoType(videoItem, firstVideo);
@@ -314,32 +304,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                     return movie;
                 }
             }
-            
-            var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType);
 
             var supportsMultiVersion = !string.Equals(collectionType, CollectionType.HomeVideos) &&
-                                       !string.Equals(collectionType, CollectionType.MusicVideos);
-
-            // Test for multi-editions
-            if (result.Items.Count > 1 && supportsMultiVersion)
-            {
-                var filenamePrefix = Path.GetFileName(path);
-
-                if (!string.IsNullOrWhiteSpace(filenamePrefix))
-                {
-                    if (result.Items.All(i => _fileSystem.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase)))
-                    {
-                        var movie = (T)result.Items[0];
-                        movie.Name = filenamePrefix;
-                        movie.LocalAlternateVersions = result.Items.Skip(1).Select(i => i.Path).ToList();
-                        movie.IsInMixedFolder = false;
-
-                        _logger.Debug("Multi-version video found: " + movie.Path);
+                                    !string.Equals(collectionType, CollectionType.MusicVideos);
 
-                        return movie;
-                    }
-                }
-            }
+            var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType, supportsMultiVersion);
 
             if (result.Items.Count == 1)
             {

+ 4 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -41,6 +41,10 @@
     "LabelCancelled": "(cancelled)",
     "LabelFailed": "(failed)",
     "ButtonHelp": "Help",
+    "HeaderLibraryAccess": "Library Access",
+    "HeaderChannelAccess": "Channel Access",
+    "HeaderDeviceAccess": "Device Access",
+    "HeaderSelectDevices": "Select Devices",
     "LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
     "LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
     "HeaderDeleteTaskTrigger": "Delete Task Trigger",

+ 4 - 0
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -63,12 +63,16 @@
     "TabPreferences": "Preferences",
     "TabPassword": "Password",
     "TabLibraryAccess": "Library Access",
+    "TabAccess": "Access",
     "TabImage": "Image",
     "TabProfile": "Profile",
     "TabMetadata": "Metadata",
     "TabImages": "Images",
     "TabNotifications": "Notifications",
     "TabCollectionTitles": "Titles",
+    "HeaderDeviceAccess": "Device Access",
+    "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+    "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access.",
     "LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
     "LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
     "HeaderVideoPlaybackSettings": "Video Playback Settings",

+ 1 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -51,7 +51,7 @@
     </Reference>
     <Reference Include="MediaBrowser.Naming, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\MediaBrowser.Naming.1.0.0.23\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+      <HintPath>..\packages\MediaBrowser.Naming.1.0.0.24\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
     </Reference>
     <Reference Include="Mono.Nat, Version=1.2.21.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>

+ 8 - 0
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1191,6 +1191,14 @@ namespace MediaBrowser.Server.Implementations.Session
             var user = _userManager.Users
                 .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
 
+            if (user != null && !string.IsNullOrWhiteSpace(request.DeviceId))
+            {
+                if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), request.DeviceId))
+                {
+                    throw new UnauthorizedAccessException("User is not allowed access from this device.");
+                }
+            }
+
             var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
 
             if (!result)

+ 1 - 1
MediaBrowser.Server.Implementations/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.Naming" version="1.0.0.23" targetFramework="net45" />
+  <package id="MediaBrowser.Naming" version="1.0.0.24" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
   <package id="morelinq" version="1.1.0" targetFramework="net45" />
 </packages>

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -558,7 +558,7 @@ namespace MediaBrowser.Server.Startup.Common
             var authContext = new AuthorizationContext(AuthenticationRepository);
             RegisterSingleInstance<IAuthorizationContext>(authContext);
             RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
-            RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager));
+            RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager));
 
             RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer));
 

+ 2 - 3
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.537</version>
+        <version>3.0.538</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,10 +12,9 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.537" />
+            <dependency id="MediaBrowser.Common" version="3.0.538" />
             <dependency id="NLog" version="3.1.0.0" />
             <dependency id="SimpleInjector" version="2.6.1" />
-            <dependency id="sharpcompress" version="0.10.2" />
         </dependencies>
     </metadata>
     <files>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.537</version>
+        <version>3.0.538</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.537</version>
+        <version>3.0.538</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.537</version>
+        <version>3.0.538</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.537" />
+            <dependency id="MediaBrowser.Common" version="3.0.538" />
         </dependencies>
     </metadata>
     <files>