Browse Source

revise endpoint attributes

Luke Pulverenti 10 năm trước cách đây
mục cha
commit
a4b75934e5
59 tập tin đã thay đổi với 712 bổ sung492 xóa
  1. 0 1
      MediaBrowser.Api/BaseApiService.cs
  2. 8 9
      MediaBrowser.Api/ConfigurationService.cs
  3. 7 2
      MediaBrowser.Api/Devices/DeviceService.cs
  4. 0 3
      MediaBrowser.Api/DisplayPreferencesService.cs
  5. 1 1
      MediaBrowser.Api/Dlna/DlnaService.cs
  6. 1 1
      MediaBrowser.Api/EnvironmentService.cs
  7. 10 12
      MediaBrowser.Api/Images/ImageByNameService.cs
  8. 2 10
      MediaBrowser.Api/Images/ImageService.cs
  9. 8 94
      MediaBrowser.Api/Images/RemoteImageService.cs
  10. 6 19
      MediaBrowser.Api/ItemLookupService.cs
  11. 1 2
      MediaBrowser.Api/ItemRefreshService.cs
  12. 6 11
      MediaBrowser.Api/Library/FileOrganizationService.cs
  13. 26 27
      MediaBrowser.Api/Library/LibraryService.cs
  14. 2 3
      MediaBrowser.Api/Library/LibraryStructureService.cs
  15. 1 0
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  16. 1 1
      MediaBrowser.Api/LocalizationService.cs
  17. 2 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  18. 1 1
      MediaBrowser.Api/PackageService.cs
  19. 0 1
      MediaBrowser.Api/PlaylistService.cs
  20. 9 11
      MediaBrowser.Api/PluginService.cs
  21. 1 1
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  22. 4 0
      MediaBrowser.Api/Session/SessionsService.cs
  23. 163 0
      MediaBrowser.Api/StartupWizardService.cs
  24. 1 1
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  25. 1 1
      MediaBrowser.Api/System/ActivityLogService.cs
  26. 3 2
      MediaBrowser.Api/System/SystemService.cs
  27. 1 2
      MediaBrowser.Api/TvShowsService.cs
  28. 2 4
      MediaBrowser.Api/UserLibrary/GameGenresService.cs
  29. 2 4
      MediaBrowser.Api/UserLibrary/GenresService.cs
  30. 2 4
      MediaBrowser.Api/UserLibrary/MusicGenresService.cs
  31. 2 4
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  32. 8 16
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  33. 9 28
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  34. 3 31
      MediaBrowser.Api/UserService.cs
  35. 3 1
      MediaBrowser.Api/VideosService.cs
  36. 1 1
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  37. 3 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  38. 12 3
      MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
  39. 3 6
      MediaBrowser.Controller/Net/IAuthService.cs
  40. 9 3
      MediaBrowser.Controller/Net/IAuthorizationContext.cs
  41. 15 0
      MediaBrowser.Controller/Net/IServiceRequest.cs
  42. 5 4
      MediaBrowser.Controller/Net/ISessionContext.cs
  43. 3 1
      MediaBrowser.Controller/Net/LoggedAttribute.cs
  44. 21 0
      MediaBrowser.Controller/Net/SecurityException.cs
  45. 62 0
      MediaBrowser.Controller/Net/ServiceStackServiceRequest.cs
  46. 0 6
      MediaBrowser.Controller/Session/ISessionManager.cs
  47. 4 7
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  48. 1 1
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  49. 85 73
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
  50. 50 7
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  51. 14 2
      MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs
  52. 3 2
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  53. 0 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  54. 0 16
      MediaBrowser.Server.Implementations/Security/AuthenticationException.cs
  55. 2 46
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  56. 4 2
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  57. 116 0
      MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs
  58. 1 0
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  59. 1 2
      MediaBrowser.ServerApplication/MainStartup.cs

+ 0 - 1
MediaBrowser.Api/BaseApiService.cs

@@ -84,7 +84,6 @@ namespace MediaBrowser.Api
         /// Gets the session.
         /// Gets the session.
         /// </summary>
         /// </summary>
         /// <returns>SessionInfo.</returns>
         /// <returns>SessionInfo.</returns>
-        /// <exception cref="System.ArgumentException">Session not found.</exception>
         protected SessionInfo GetSession()
         protected SessionInfo GetSession()
         {
         {
             var session = SessionContext.GetSession(Request);
             var session = SessionContext.GetSession(Request);

+ 8 - 9
MediaBrowser.Api/ConfigurationService.cs

@@ -9,7 +9,6 @@ using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using ServiceStack;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using ServiceStack.Web;
 using ServiceStack.Web;
-using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -33,18 +32,18 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Key { get; set; }
         public string Key { get; set; }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Class UpdateConfiguration
     /// Class UpdateConfiguration
     /// </summary>
     /// </summary>
     [Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
     [Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class UpdateConfiguration : ServerConfiguration, IReturnVoid
     public class UpdateConfiguration : ServerConfiguration, IReturnVoid
     {
     {
     }
     }
 
 
     [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
     [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
     public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
     {
     {
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -52,23 +51,23 @@ namespace MediaBrowser.Api
 
 
         public Stream RequestStream { get; set; }
         public Stream RequestStream { get; set; }
     }
     }
-    
+
     [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
     [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
     public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
     {
     {
 
 
     }
     }
 
 
     [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
     [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
     public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
     {
     {
 
 
     }
     }
 
 
     [Route("/System/Configuration/MetadataPlugins/Autoset", "POST")]
     [Route("/System/Configuration/MetadataPlugins/Autoset", "POST")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
     public class AutoSetMetadataOptions : IReturnVoid
     public class AutoSetMetadataOptions : IReturnVoid
     {
     {
 
 
@@ -149,7 +148,7 @@ namespace MediaBrowser.Api
 
 
             var configurationType = _configurationManager.GetConfigurationType(key);
             var configurationType = _configurationManager.GetConfigurationType(key);
             var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, configurationType);
             var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, configurationType);
-            
+
             _configurationManager.SaveConfiguration(key, configuration);
             _configurationManager.SaveConfiguration(key, configuration);
         }
         }
 
 

+ 7 - 2
MediaBrowser.Api/Devices/DeviceService.cs

@@ -12,6 +12,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Api.Devices
 namespace MediaBrowser.Api.Devices
 {
 {
     [Route("/Devices", "GET", Summary = "Gets all devices")]
     [Route("/Devices", "GET", Summary = "Gets all devices")]
+    [Authenticated(Roles = "Admin")]
     public class GetDevices : IReturn<List<DeviceInfo>>
     public class GetDevices : IReturn<List<DeviceInfo>>
     {
     {
         [ApiMember(Name = "SupportsContentUploading", Description = "SupportsContentUploading", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "SupportsContentUploading", Description = "SupportsContentUploading", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -26,6 +27,7 @@ namespace MediaBrowser.Api.Devices
     }
     }
 
 
     [Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")]
     [Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")]
+    [Authenticated]
     public class GetCameraUploads : IReturn<ContentUploadHistory>
     public class GetCameraUploads : IReturn<ContentUploadHistory>
     {
     {
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -33,6 +35,7 @@ namespace MediaBrowser.Api.Devices
     }
     }
 
 
     [Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")]
     [Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")]
+    [Authenticated]
     public class PostCameraUpload : IRequiresRequestStream, IReturnVoid
     public class PostCameraUpload : IRequiresRequestStream, IReturnVoid
     {
     {
         [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -51,6 +54,7 @@ namespace MediaBrowser.Api.Devices
     }
     }
 
 
     [Route("/Devices/Info", "GET", Summary = "Gets device info")]
     [Route("/Devices/Info", "GET", Summary = "Gets device info")]
+    [Authenticated]
     public class GetDeviceInfo : IReturn<DeviceInfo>
     public class GetDeviceInfo : IReturn<DeviceInfo>
     {
     {
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -58,6 +62,7 @@ namespace MediaBrowser.Api.Devices
     }
     }
 
 
     [Route("/Devices/Capabilities", "GET", Summary = "Gets device capabilities")]
     [Route("/Devices/Capabilities", "GET", Summary = "Gets device capabilities")]
+    [Authenticated]
     public class GetDeviceCapabilities : IReturn<ClientCapabilities>
     public class GetDeviceCapabilities : IReturn<ClientCapabilities>
     {
     {
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -65,13 +70,13 @@ namespace MediaBrowser.Api.Devices
     }
     }
 
 
     [Route("/Devices/Options", "POST", Summary = "Updates device options")]
     [Route("/Devices/Options", "POST", Summary = "Updates device options")]
+    [Authenticated(Roles = "Admin")]
     public class PostDeviceOptions : DeviceOptions, IReturnVoid
     public class PostDeviceOptions : DeviceOptions, IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
-    
-    [Authenticated]
+
     public class DeviceService : BaseApiService
     public class DeviceService : BaseApiService
     {
     {
         private readonly IDeviceManager _deviceManager;
         private readonly IDeviceManager _deviceManager;

+ 0 - 3
MediaBrowser.Api/DisplayPreferencesService.cs

@@ -24,9 +24,6 @@ namespace MediaBrowser.Api
 
 
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         public Guid UserId { get; set; }
         public Guid UserId { get; set; }
-
-        [ApiMember(Name = "Client", Description = "Client", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Client { get; set; }
     }
     }
 
 
     [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")]
     [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")]

+ 1 - 1
MediaBrowser.Api/Dlna/DlnaService.cs

@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.Dlna
     {
     {
     }
     }
 
 
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class DlnaService : BaseApiService
     public class DlnaService : BaseApiService
     {
     {
         private readonly IDlnaManager _dlnaManager;
         private readonly IDlnaManager _dlnaManager;

+ 1 - 1
MediaBrowser.Api/EnvironmentService.cs

@@ -87,7 +87,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// <summary>
     /// Class EnvironmentService
     /// Class EnvironmentService
     /// </summary>
     /// </summary>
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class EnvironmentService : BaseApiService
     public class EnvironmentService : BaseApiService
     {
     {
         const char UncSeparator = '\\';
         const char UncSeparator = '\\';

+ 10 - 12
MediaBrowser.Api/Images/ImageByNameService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
@@ -14,8 +15,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class GetGeneralImage
     /// Class GetGeneralImage
     /// </summary>
     /// </summary>
-    [Route("/Images/General/{Name}/{Type}", "GET")]
-    [Api(Description = "Gets a general image by name")]
+    [Route("/Images/General/{Name}/{Type}", "GET", Summary = "Gets a general image by name")]
     public class GetGeneralImage
     public class GetGeneralImage
     {
     {
         /// <summary>
         /// <summary>
@@ -32,8 +32,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class GetRatingImage
     /// Class GetRatingImage
     /// </summary>
     /// </summary>
-    [Route("/Images/Ratings/{Theme}/{Name}", "GET")]
-    [Api(Description = "Gets a rating image by name")]
+    [Route("/Images/Ratings/{Theme}/{Name}", "GET", Summary = "Gets a rating image by name")]
     public class GetRatingImage
     public class GetRatingImage
     {
     {
         /// <summary>
         /// <summary>
@@ -54,8 +53,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class GetMediaInfoImage
     /// Class GetMediaInfoImage
     /// </summary>
     /// </summary>
-    [Route("/Images/MediaInfo/{Theme}/{Name}", "GET")]
-    [Api(Description = "Gets a media info image by name")]
+    [Route("/Images/MediaInfo/{Theme}/{Name}", "GET", Summary = "Gets a media info image by name")]
     public class GetMediaInfoImage
     public class GetMediaInfoImage
     {
     {
         /// <summary>
         /// <summary>
@@ -73,20 +71,20 @@ namespace MediaBrowser.Api.Images
         public string Theme { get; set; }
         public string Theme { get; set; }
     }
     }
 
 
-    [Route("/Images/MediaInfo", "GET")]
-    [Api(Description = "Gets all media info image by name")]
+    [Route("/Images/MediaInfo", "GET", Summary = "Gets all media info image by name")]
+    [Authenticated]
     public class GetMediaInfoImages : IReturn<List<ImageByNameInfo>>
     public class GetMediaInfoImages : IReturn<List<ImageByNameInfo>>
     {
     {
     }
     }
 
 
-    [Route("/Images/Ratings", "GET")]
-    [Api(Description = "Gets all rating images by name")]
+    [Route("/Images/Ratings", "GET", Summary = "Gets all rating images by name")]
+    [Authenticated]
     public class GetRatingImages : IReturn<List<ImageByNameInfo>>
     public class GetRatingImages : IReturn<List<ImageByNameInfo>>
     {
     {
     }
     }
 
 
-    [Route("/Images/General", "GET")]
-    [Api(Description = "Gets all general images by name")]
+    [Route("/Images/General", "GET", Summary = "Gets all general images by name")]
+    [Authenticated]
     public class GetGeneralImages : IReturn<List<ImageByNameInfo>>
     public class GetGeneralImages : IReturn<List<ImageByNameInfo>>
     {
     {
     }
     }

+ 2 - 10
MediaBrowser.Api/Images/ImageService.cs

@@ -24,8 +24,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class GetItemImage
     /// Class GetItemImage
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/Images", "GET")]
-    [Api(Description = "Gets information about an item's images")]
+    [Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")]
     [Authenticated]
     [Authenticated]
     public class GetItemImageInfos : IReturn<List<ImageInfo>>
     public class GetItemImageInfos : IReturn<List<ImageInfo>>
     {
     {
@@ -43,7 +42,6 @@ namespace MediaBrowser.Api.Images
     [Route("/Items/{Id}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "GET")]
     [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "GET")]
     [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "HEAD")]
     [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "HEAD")]
-    [Api(Description = "Gets an item image")]
     public class GetItemImage : ImageRequest
     public class GetItemImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -57,8 +55,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class UpdateItemImageIndex
     /// Class UpdateItemImageIndex
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")]
-    [Api(Description = "Updates the index for an item image")]
+    [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST", Summary = "Updates the index for an item image")]
     [Authenticated]
     [Authenticated]
     public class UpdateItemImageIndex : IReturnVoid
     public class UpdateItemImageIndex : IReturnVoid
     {
     {
@@ -122,7 +119,6 @@ namespace MediaBrowser.Api.Images
     [Route("/Studios/{Name}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Studios/{Name}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Years/{Year}/Images/{Type}", "HEAD")]
     [Route("/Years/{Year}/Images/{Type}", "HEAD")]
     [Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")]
-    [Api(Description = "Gets an item by name image")]
     public class GetItemByNameImage : ImageRequest
     public class GetItemByNameImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -140,7 +136,6 @@ namespace MediaBrowser.Api.Images
     [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
     [Route("/Users/{Id}/Images/{Type}", "HEAD")]
     [Route("/Users/{Id}/Images/{Type}", "HEAD")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "HEAD")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "HEAD")]
-    [Api(Description = "Gets a user image")]
     public class GetUserImage : ImageRequest
     public class GetUserImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -156,7 +151,6 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Items/{Id}/Images/{Type}", "DELETE")]
     [Route("/Items/{Id}/Images/{Type}", "DELETE")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
-    [Api(Description = "Deletes an item image")]
     [Authenticated]
     [Authenticated]
     public class DeleteItemImage : DeleteImageRequest, IReturnVoid
     public class DeleteItemImage : DeleteImageRequest, IReturnVoid
     {
     {
@@ -173,7 +167,6 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}/Images/{Type}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
-    [Api(Description = "Deletes a user image")]
     [Authenticated]
     [Authenticated]
     public class DeleteUserImage : DeleteImageRequest, IReturnVoid
     public class DeleteUserImage : DeleteImageRequest, IReturnVoid
     {
     {
@@ -190,7 +183,6 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}/Images/{Type}", "POST")]
     [Route("/Users/{Id}/Images/{Type}", "POST")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
-    [Api(Description = "Posts a user image")]
     [Authenticated]
     [Authenticated]
     public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     {
     {

+ 8 - 94
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -5,11 +5,11 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using ServiceStack;
 using ServiceStack;
-using ServiceStack.Text.Controller;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
@@ -45,8 +45,8 @@ namespace MediaBrowser.Api.Images
         public bool IncludeAllLanguages { get; set; }
         public bool IncludeAllLanguages { get; set; }
     }
     }
 
 
-    [Route("/Items/{Id}/RemoteImages", "GET")]
-    [Api(Description = "Gets available remote images for an item")]
+    [Route("/Items/{Id}/RemoteImages", "GET", Summary = "Gets available remote images for an item")]
+    [Authenticated]
     public class GetRemoteImages : BaseRemoteImageRequest
     public class GetRemoteImages : BaseRemoteImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -57,25 +57,8 @@ namespace MediaBrowser.Api.Images
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Artists/{Name}/RemoteImages", "GET")]
-    [Route("/Genres/{Name}/RemoteImages", "GET")]
-    [Route("/GameGenres/{Name}/RemoteImages", "GET")]
-    [Route("/MusicGenres/{Name}/RemoteImages", "GET")]
-    [Route("/Persons/{Name}/RemoteImages", "GET")]
-    [Route("/Studios/{Name}/RemoteImages", "GET")]
-    [Api(Description = "Gets available remote images for an item")]
-    public class GetItemByNameRemoteImages : BaseRemoteImageRequest
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Name { get; set; }
-    }
-
-    [Route("/Items/{Id}/RemoteImages/Providers", "GET")]
-    [Api(Description = "Gets available remote image providers for an item")]
+    [Route("/Items/{Id}/RemoteImages/Providers", "GET", Summary = "Gets available remote image providers for an item")]
+    [Authenticated]
     public class GetRemoteImageProviders : IReturn<List<ImageProviderInfo>>
     public class GetRemoteImageProviders : IReturn<List<ImageProviderInfo>>
     {
     {
         /// <summary>
         /// <summary>
@@ -86,23 +69,6 @@ namespace MediaBrowser.Api.Images
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Artists/{Name}/RemoteImages/Providers", "GET")]
-    [Route("/Genres/{Name}/RemoteImages/Providers", "GET")]
-    [Route("/GameGenres/{Name}/RemoteImages/Providers", "GET")]
-    [Route("/MusicGenres/{Name}/RemoteImages/Providers", "GET")]
-    [Route("/Persons/{Name}/RemoteImages/Providers", "GET")]
-    [Route("/Studios/{Name}/RemoteImages/Providers", "GET")]
-    [Api(Description = "Gets available remote image providers for an item")]
-    public class GetItemByNameRemoteImageProviders : IReturn<List<ImageProviderInfo>>
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Name { get; set; }
-    }
-
     public class BaseDownloadRemoteImage : IReturnVoid
     public class BaseDownloadRemoteImage : IReturnVoid
     {
     {
         [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -115,8 +81,8 @@ namespace MediaBrowser.Api.Images
         public string ImageUrl { get; set; }
         public string ImageUrl { get; set; }
     }
     }
 
 
-    [Route("/Items/{Id}/RemoteImages/Download", "POST")]
-    [Api(Description = "Downloads a remote image for an item")]
+    [Route("/Items/{Id}/RemoteImages/Download", "POST", Summary = "Downloads a remote image for an item")]
+    [Authenticated(Roles="Admin")]
     public class DownloadRemoteImage : BaseDownloadRemoteImage
     public class DownloadRemoteImage : BaseDownloadRemoteImage
     {
     {
         /// <summary>
         /// <summary>
@@ -127,25 +93,7 @@ namespace MediaBrowser.Api.Images
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Artists/{Name}/RemoteImages/Download", "POST")]
-    [Route("/Genres/{Name}/RemoteImages/Download", "POST")]
-    [Route("/GameGenres/{Name}/RemoteImages/Download", "POST")]
-    [Route("/MusicGenres/{Name}/RemoteImages/Download", "POST")]
-    [Route("/Persons/{Name}/RemoteImages/Download", "POST")]
-    [Route("/Studios/{Name}/RemoteImages/Download", "POST")]
-    [Api(Description = "Downloads a remote image for an item")]
-    public class DownloadItemByNameRemoteImage : BaseDownloadRemoteImage
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Name { get; set; }
-    }
-
-    [Route("/Images/Remote", "GET")]
-    [Api(Description = "Gets a remote image")]
+    [Route("/Images/Remote", "GET", Summary = "Gets a remote image")]
     public class GetRemoteImage
     public class GetRemoteImage
     {
     {
         [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -182,18 +130,6 @@ namespace MediaBrowser.Api.Images
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
-        public object Get(GetItemByNameRemoteImageProviders request)
-        {
-            var pathInfo = PathInfo.Parse(Request.PathInfo);
-            var type = pathInfo.GetArgumentValue<string>(0);
-
-            var item = GetItemByName(request.Name, type, _libraryManager);
-
-            var result = GetImageProviders(item);
-
-            return ToOptimizedSerializedResultUsingCache(result);
-        }
-
         private List<ImageProviderInfo> GetImageProviders(BaseItem item)
         private List<ImageProviderInfo> GetImageProviders(BaseItem item)
         {
         {
             return _providerManager.GetRemoteImageProviderInfo(item).ToList();
             return _providerManager.GetRemoteImageProviderInfo(item).ToList();
@@ -206,16 +142,6 @@ namespace MediaBrowser.Api.Images
             return await GetRemoteImageResult(item, request).ConfigureAwait(false);
             return await GetRemoteImageResult(item, request).ConfigureAwait(false);
         }
         }
 
 
-        public async Task<object> Get(GetItemByNameRemoteImages request)
-        {
-            var pathInfo = PathInfo.Parse(Request.PathInfo);
-            var type = pathInfo.GetArgumentValue<string>(0);
-
-            var item = GetItemByName(request.Name, type, _libraryManager);
-
-            return await GetRemoteImageResult(item, request).ConfigureAwait(false);
-        }
-
         private async Task<RemoteImageResult> GetRemoteImageResult(BaseItem item, BaseRemoteImageRequest request)
         private async Task<RemoteImageResult> GetRemoteImageResult(BaseItem item, BaseRemoteImageRequest request)
         {
         {
             var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
             var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
@@ -274,18 +200,6 @@ namespace MediaBrowser.Api.Images
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
 
 
-        public void Post(DownloadItemByNameRemoteImage request)
-        {
-            var pathInfo = PathInfo.Parse(Request.PathInfo);
-            var type = pathInfo.GetArgumentValue<string>(0);
-
-            var item = GetItemByName(request.Name, type, _libraryManager);
-
-            var task = DownloadRemoteImage(item, request);
-
-            Task.WaitAll(task);
-        }
-
         /// <summary>
         /// <summary>
         /// Downloads the remote image.
         /// Downloads the remote image.
         /// </summary>
         /// </summary>

+ 6 - 19
MediaBrowser.Api/ItemLookupService.cs

@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
@@ -20,9 +19,8 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
-    [Route("/Items/{Id}/ExternalIdInfos", "GET")]
-    [Api(Description = "Gets external id infos for an item")]
-    [Authenticated]
+    [Route("/Items/{Id}/ExternalIdInfos", "GET", Summary = "Gets external id infos for an item")]
+    [Authenticated(Roles = "Admin")]
     public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
     public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
     {
     {
         /// <summary>
         /// <summary>
@@ -34,70 +32,60 @@ namespace MediaBrowser.Api
     }
     }
 
 
     [Route("/Items/RemoteSearch/Movie", "POST")]
     [Route("/Items/RemoteSearch/Movie", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
     public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/Trailer", "POST")]
     [Route("/Items/RemoteSearch/Trailer", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
     public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/AdultVideo", "POST")]
     [Route("/Items/RemoteSearch/AdultVideo", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
     public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/Series", "POST")]
     [Route("/Items/RemoteSearch/Series", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>>
     public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/Game", "POST")]
     [Route("/Items/RemoteSearch/Game", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetGameRemoteSearchResults : RemoteSearchQuery<GameInfo>, IReturn<List<RemoteSearchResult>>
     public class GetGameRemoteSearchResults : RemoteSearchQuery<GameInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/BoxSet", "POST")]
     [Route("/Items/RemoteSearch/BoxSet", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>>
     public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/MusicArtist", "POST")]
     [Route("/Items/RemoteSearch/MusicArtist", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>>
     public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/MusicAlbum", "POST")]
     [Route("/Items/RemoteSearch/MusicAlbum", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
     [Authenticated]
     [Authenticated]
     public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>>
     public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
     [Route("/Items/RemoteSearch/Person", "POST")]
     [Route("/Items/RemoteSearch/Person", "POST")]
-    [Api(Description = "Gets external id infos for an item")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
     public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
     {
     {
     }
     }
 
 
-    [Route("/Items/RemoteSearch/Image", "GET")]
-    [Api(Description = "Gets a remote image")]
+    [Route("/Items/RemoteSearch/Image", "GET", Summary = "Gets a remote image")]
     public class GetRemoteSearchImage
     public class GetRemoteSearchImage
     {
     {
         [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -107,9 +95,8 @@ namespace MediaBrowser.Api
         public string ProviderName { get; set; }
         public string ProviderName { get; set; }
     }
     }
 
 
-    [Route("/Items/RemoteSearch/Apply/{Id}", "POST")]
-    [Api(Description = "Applies search criteria to an item and refreshes metadata")]
-    [Authenticated]
+    [Route("/Items/RemoteSearch/Apply/{Id}", "POST", Summary = "Applies search criteria to an item and refreshes metadata")]
+    [Authenticated(Roles = "Admin")]
     public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid
     public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]

+ 1 - 2
MediaBrowser.Api/ItemRefreshService.cs

@@ -26,8 +26,7 @@ namespace MediaBrowser.Api
         public bool ReplaceAllImages { get; set; }
         public bool ReplaceAllImages { get; set; }
     }
     }
 
 
-    [Route("/Items/{Id}/Refresh", "POST")]
-    [Api(Description = "Refreshes metadata for an item")]
+    [Route("/Items/{Id}/Refresh", "POST", Summary = "Refreshes metadata for an item")]
     public class RefreshItem : BaseRefreshRequest
     public class RefreshItem : BaseRefreshRequest
     {
     {
         [ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]

+ 6 - 11
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -7,8 +7,7 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api.Library
 namespace MediaBrowser.Api.Library
 {
 {
-    [Route("/Library/FileOrganization", "GET")]
-    [Api(Description = "Gets file organization results")]
+    [Route("/Library/FileOrganization", "GET", Summary = "Gets file organization results")]
     public class GetFileOrganizationActivity : IReturn<QueryResult<FileOrganizationResult>>
     public class GetFileOrganizationActivity : IReturn<QueryResult<FileOrganizationResult>>
     {
     {
         /// <summary>
         /// <summary>
@@ -26,14 +25,12 @@ namespace MediaBrowser.Api.Library
         public int? Limit { get; set; }
         public int? Limit { get; set; }
     }
     }
 
 
-    [Route("/Library/FileOrganizations", "DELETE")]
-    [Api(Description = "Clears the activity log")]
+    [Route("/Library/FileOrganizations", "DELETE", Summary = "Clears the activity log")]
     public class ClearOrganizationLog : IReturnVoid
     public class ClearOrganizationLog : IReturnVoid
     {
     {
     }
     }
 
 
-    [Route("/Library/FileOrganizations/{Id}/File", "DELETE")]
-    [Api(Description = "Deletes the original file of a organizer result")]
+    [Route("/Library/FileOrganizations/{Id}/File", "DELETE", Summary = "Deletes the original file of a organizer result")]
     public class DeleteOriginalFile : IReturnVoid
     public class DeleteOriginalFile : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -44,8 +41,7 @@ namespace MediaBrowser.Api.Library
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Library/FileOrganizations/{Id}/Organize", "POST")]
-    [Api(Description = "Performs an organization")]
+    [Route("/Library/FileOrganizations/{Id}/Organize", "POST", Summary = "Performs an organization")]
     public class PerformOrganization : IReturn<QueryResult<FileOrganizationResult>>
     public class PerformOrganization : IReturn<QueryResult<FileOrganizationResult>>
     {
     {
         /// <summary>
         /// <summary>
@@ -56,8 +52,7 @@ namespace MediaBrowser.Api.Library
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Library/FileOrganizations/{Id}/Episode/Organize", "POST")]
-    [Api(Description = "Performs an organization")]
+    [Route("/Library/FileOrganizations/{Id}/Episode/Organize", "POST", Summary = "Performs an organization")]
     public class OrganizeEpisode
     public class OrganizeEpisode
     {
     {
         [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
@@ -79,7 +74,7 @@ namespace MediaBrowser.Api.Library
         public bool RememberCorrection { get; set; }
         public bool RememberCorrection { get; set; }
     }
     }
 
 
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class FileOrganizationService : BaseApiService
     public class FileOrganizationService : BaseApiService
     {
     {
         private readonly IFileOrganizationService _iFileOrganizationService;
         private readonly IFileOrganizationService _iFileOrganizationService;

+ 26 - 27
MediaBrowser.Api/Library/LibraryService.cs

@@ -23,8 +23,8 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api.Library
 namespace MediaBrowser.Api.Library
 {
 {
-    [Route("/Items/{Id}/File", "GET")]
-    [Api(Description = "Gets the original file of an item")]
+    [Route("/Items/{Id}/File", "GET", Summary = "Gets the original file of an item")]
+    [Authenticated]
     public class GetFile
     public class GetFile
     {
     {
         /// <summary>
         /// <summary>
@@ -38,8 +38,8 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class GetCriticReviews
     /// Class GetCriticReviews
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/CriticReviews", "GET")]
-    [Api(Description = "Gets critic reviews for an item")]
+    [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")]
+    [Authenticated]
     public class GetCriticReviews : IReturn<QueryResult<ItemReview>>
     public class GetCriticReviews : IReturn<QueryResult<ItemReview>>
     {
     {
         /// <summary>
         /// <summary>
@@ -67,8 +67,8 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class GetThemeSongs
     /// Class GetThemeSongs
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/ThemeSongs", "GET")]
-    [Api(Description = "Gets theme songs for an item")]
+    [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")]
+    [Authenticated]
     public class GetThemeSongs : IReturn<ThemeMediaResult>
     public class GetThemeSongs : IReturn<ThemeMediaResult>
     {
     {
         /// <summary>
         /// <summary>
@@ -92,8 +92,8 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class GetThemeVideos
     /// Class GetThemeVideos
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/ThemeVideos", "GET")]
-    [Api(Description = "Gets theme videos for an item")]
+    [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")]
+    [Authenticated]
     public class GetThemeVideos : IReturn<ThemeMediaResult>
     public class GetThemeVideos : IReturn<ThemeMediaResult>
     {
     {
         /// <summary>
         /// <summary>
@@ -117,8 +117,8 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class GetThemeVideos
     /// Class GetThemeVideos
     /// </summary>
     /// </summary>
-    [Route("/Items/{Id}/ThemeMedia", "GET")]
-    [Api(Description = "Gets theme videos and songs for an item")]
+    [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")]
+    [Authenticated]
     public class GetThemeMedia : IReturn<AllThemeMediaResult>
     public class GetThemeMedia : IReturn<AllThemeMediaResult>
     {
     {
         /// <summary>
         /// <summary>
@@ -139,14 +139,14 @@ namespace MediaBrowser.Api.Library
         public bool InheritFromParent { get; set; }
         public bool InheritFromParent { get; set; }
     }
     }
 
 
-    [Route("/Library/Refresh", "POST")]
-    [Api(Description = "Starts a library scan")]
+    [Route("/Library/Refresh", "POST", Summary = "Starts a library scan")]
+    [Authenticated(Roles = "Admin")]
     public class RefreshLibrary : IReturnVoid
     public class RefreshLibrary : IReturnVoid
     {
     {
     }
     }
 
 
-    [Route("/Items/{Id}", "DELETE")]
-    [Api(Description = "Deletes an item from the library and file system")]
+    [Route("/Items/{Id}", "DELETE", Summary = "Deletes an item from the library and file system")]
+    [Authenticated(Roles = "Delete")]
     public class DeleteItem : IReturnVoid
     public class DeleteItem : IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
@@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Items/Counts", "GET")]
     [Route("/Items/Counts", "GET")]
-    [Api(Description = "Gets counts of various item types")]
+    [Authenticated]
     public class GetItemCounts : IReturn<ItemCounts>
     public class GetItemCounts : IReturn<ItemCounts>
     {
     {
         [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -164,8 +164,8 @@ namespace MediaBrowser.Api.Library
         public bool? IsFavorite { get; set; }
         public bool? IsFavorite { get; set; }
     }
     }
 
 
-    [Route("/Items/{Id}/Ancestors", "GET")]
-    [Api(Description = "Gets all parents of an item")]
+    [Route("/Items/{Id}/Ancestors", "GET", Summary = "Gets all parents of an item")]
+    [Authenticated]
     public class GetAncestors : IReturn<BaseItemDto[]>
     public class GetAncestors : IReturn<BaseItemDto[]>
     {
     {
         /// <summary>
         /// <summary>
@@ -183,8 +183,8 @@ namespace MediaBrowser.Api.Library
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Items/YearIndex", "GET")]
-    [Api(Description = "Gets a year index based on an item query.")]
+    [Route("/Items/YearIndex", "GET", Summary = "Gets a year index based on an item query.")]
+    [Authenticated]
     public class GetYearIndex : IReturn<List<ItemIndex>>
     public class GetYearIndex : IReturn<List<ItemIndex>>
     {
     {
         /// <summary>
         /// <summary>
@@ -201,23 +201,23 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class GetPhyscialPaths
     /// Class GetPhyscialPaths
     /// </summary>
     /// </summary>
-    [Route("/Library/PhysicalPaths", "GET")]
-    [Api(Description = "Gets a list of physical paths from virtual folders")]
+    [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")]
+    [Authenticated(Roles = "Admin")]
     public class GetPhyscialPaths : IReturn<List<string>>
     public class GetPhyscialPaths : IReturn<List<string>>
     {
     {
     }
     }
 
 
-    [Route("/Library/MediaFolders", "GET")]
-    [Api(Description = "Gets all user media folders.")]
+    [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")]
+    [Authenticated]
     public class GetMediaFolders : IReturn<ItemsResult>
     public class GetMediaFolders : IReturn<ItemsResult>
     {
     {
         [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         public bool? IsHidden { get; set; }
         public bool? IsHidden { get; set; }
     }
     }
 
 
-    [Route("/Library/Series/Added", "POST")]
-    [Route("/Library/Series/Updated", "POST")]
-    [Api(Description = "Reports that new episodes of a series have been added by an external source")]
+    [Route("/Library/Series/Added", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
+    [Route("/Library/Series/Updated", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
+    [Authenticated]
     public class PostUpdatedSeries : IReturnVoid
     public class PostUpdatedSeries : IReturnVoid
     {
     {
         [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -227,7 +227,6 @@ namespace MediaBrowser.Api.Library
     /// <summary>
     /// <summary>
     /// Class LibraryService
     /// Class LibraryService
     /// </summary>
     /// </summary>
-    [Authenticated]
     public class LibraryService : BaseApiService
     public class LibraryService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 3
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -131,11 +131,11 @@ namespace MediaBrowser.Api.Library
         /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value>
         public bool RefreshLibrary { get; set; }
         public bool RefreshLibrary { get; set; }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Class LibraryStructureService
     /// Class LibraryStructureService
     /// </summary>
     /// </summary>
-    [Authenticated]
+    [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
     public class LibraryStructureService : BaseApiService
     public class LibraryStructureService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>
@@ -164,7 +164,6 @@ namespace MediaBrowser.Api.Library
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        /// <exception cref="System.ArgumentNullException">appPaths</exception>
         public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
         public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
         {
         {
             if (appPaths == null)
             if (appPaths == null)

+ 1 - 0
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -18,6 +18,7 @@ namespace MediaBrowser.Api.LiveTv
     /// This is insecure right now to avoid windows phone refactoring
     /// This is insecure right now to avoid windows phone refactoring
     /// </summary>
     /// </summary>
     [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")]
     [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")]
+    [Authenticated]
     public class GetLiveTvInfo : IReturn<LiveTvInfo>
     public class GetLiveTvInfo : IReturn<LiveTvInfo>
     {
     {
     }
     }

+ 1 - 1
MediaBrowser.Api/LocalizationService.cs

@@ -43,7 +43,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// <summary>
     /// Class CulturesService
     /// Class CulturesService
     /// </summary>
     /// </summary>
-    [Authenticated]
+    [Authenticated(AllowBeforeStartupWizard = true)]
     public class LocalizationService : BaseApiService
     public class LocalizationService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -69,6 +69,7 @@
     <Compile Include="..\SharedVersion.cs">
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="AppThemeService.cs" />
     <Compile Include="BrandingService.cs" />
     <Compile Include="BrandingService.cs" />
     <Compile Include="ChannelService.cs" />
     <Compile Include="ChannelService.cs" />
     <Compile Include="ConnectService.cs" />
     <Compile Include="ConnectService.cs" />
@@ -79,10 +80,10 @@
     <Compile Include="Library\ChapterService.cs" />
     <Compile Include="Library\ChapterService.cs" />
     <Compile Include="Playback\Hls\MpegDashService.cs" />
     <Compile Include="Playback\Hls\MpegDashService.cs" />
     <Compile Include="PlaylistService.cs" />
     <Compile Include="PlaylistService.cs" />
+    <Compile Include="StartupWizardService.cs" />
     <Compile Include="Subtitles\SubtitleService.cs" />
     <Compile Include="Subtitles\SubtitleService.cs" />
     <Compile Include="Movies\CollectionService.cs" />
     <Compile Include="Movies\CollectionService.cs" />
     <Compile Include="Music\AlbumsService.cs" />
     <Compile Include="Music\AlbumsService.cs" />
-    <Compile Include="AppThemeService.cs" />
     <Compile Include="BaseApiService.cs" />
     <Compile Include="BaseApiService.cs" />
     <Compile Include="ConfigurationService.cs" />
     <Compile Include="ConfigurationService.cs" />
     <Compile Include="DisplayPreferencesService.cs" />
     <Compile Include="DisplayPreferencesService.cs" />

+ 1 - 1
MediaBrowser.Api/PackageService.cs

@@ -122,7 +122,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// <summary>
     /// Class PackageService
     /// Class PackageService
     /// </summary>
     /// </summary>
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class PackageService : BaseApiService
     public class PackageService : BaseApiService
     {
     {
         private readonly IInstallationManager _installationManager;
         private readonly IInstallationManager _installationManager;

+ 0 - 1
MediaBrowser.Api/PlaylistService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Playlists;
 using MediaBrowser.Model.Playlists;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using ServiceStack;
-using System;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 

+ 9 - 11
MediaBrowser.Api/PluginService.cs

@@ -20,6 +20,7 @@ namespace MediaBrowser.Api
     /// Class Plugins
     /// Class Plugins
     /// </summary>
     /// </summary>
     [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")]
     [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")]
+    [Authenticated]
     public class GetPlugins : IReturn<List<PluginInfo>>
     public class GetPlugins : IReturn<List<PluginInfo>>
     {
     {
     }
     }
@@ -28,6 +29,7 @@ namespace MediaBrowser.Api
     /// Class UninstallPlugin
     /// Class UninstallPlugin
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}", "DELETE", Summary = "Uninstalls a plugin")]
     [Route("/Plugins/{Id}", "DELETE", Summary = "Uninstalls a plugin")]
+    [Authenticated(Roles = "Admin")]
     public class UninstallPlugin : IReturnVoid
     public class UninstallPlugin : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -42,6 +44,7 @@ namespace MediaBrowser.Api
     /// Class GetPluginConfiguration
     /// Class GetPluginConfiguration
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/Configuration", "GET", Summary = "Gets a plugin's configuration")]
     [Route("/Plugins/{Id}/Configuration", "GET", Summary = "Gets a plugin's configuration")]
+    [Authenticated]
     public class GetPluginConfiguration
     public class GetPluginConfiguration
     {
     {
         /// <summary>
         /// <summary>
@@ -56,6 +59,7 @@ namespace MediaBrowser.Api
     /// Class UpdatePluginConfiguration
     /// Class UpdatePluginConfiguration
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/Configuration", "POST", Summary = "Updates a plugin's configuration")]
     [Route("/Plugins/{Id}/Configuration", "POST", Summary = "Updates a plugin's configuration")]
+    [Authenticated]
     public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid
     public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -76,6 +80,7 @@ namespace MediaBrowser.Api
     /// Class GetPluginSecurityInfo
     /// Class GetPluginSecurityInfo
     /// </summary>
     /// </summary>
     [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information")]
     [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information")]
+    [Authenticated]
     public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo>
     public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo>
     {
     {
     }
     }
@@ -84,11 +89,13 @@ namespace MediaBrowser.Api
     /// Class UpdatePluginSecurityInfo
     /// Class UpdatePluginSecurityInfo
     /// </summary>
     /// </summary>
     [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information")]
     [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information")]
+    [Authenticated(Roles = "Admin")]
     public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid
     public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid
     {
     {
     }
     }
 
 
     [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature")]
     [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature")]
+    [Authenticated]
     public class GetRegistrationStatus
     public class GetRegistrationStatus
     {
     {
         [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -97,11 +104,10 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Mb2Equivalent", Description = "Optional. The equivalent feature name in MB2", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Mb2Equivalent", Description = "Optional. The equivalent feature name in MB2", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string Mb2Equivalent { get; set; }
         public string Mb2Equivalent { get; set; }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Class PluginsService
     /// Class PluginsService
     /// </summary>
     /// </summary>
-    [Authenticated]
     public class PluginService : BaseApiService
     public class PluginService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>
@@ -118,14 +124,6 @@ namespace MediaBrowser.Api
 
 
         private readonly IInstallationManager _installationManager;
         private readonly IInstallationManager _installationManager;
 
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PluginService" /> class.
-        /// </summary>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="appHost">The app host.</param>
-        /// <param name="securityManager">The security manager.</param>
-        /// <param name="installationManager">The installation manager.</param>
-        /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         public PluginService(IJsonSerializer jsonSerializer, IApplicationHost appHost, ISecurityManager securityManager, IInstallationManager installationManager)
         public PluginService(IJsonSerializer jsonSerializer, IApplicationHost appHost, ISecurityManager securityManager, IInstallationManager installationManager)
             : base()
             : base()
         {
         {
@@ -151,7 +149,7 @@ namespace MediaBrowser.Api
 
 
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Gets the specified request.
         /// Gets the specified request.
         /// </summary>
         /// </summary>

+ 1 - 1
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -79,7 +79,7 @@ namespace MediaBrowser.Api.ScheduledTasks
     /// <summary>
     /// <summary>
     /// Class ScheduledTasksService
     /// Class ScheduledTasksService
     /// </summary>
     /// </summary>
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class ScheduledTaskService : BaseApiService
     public class ScheduledTaskService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>

+ 4 - 0
MediaBrowser.Api/Session/SessionsService.cs

@@ -241,16 +241,19 @@ namespace MediaBrowser.Api.Session
     }
     }
 
 
     [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
     [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
+    [Authenticated]
     public class ReportSessionEnded : IReturnVoid
     public class ReportSessionEnded : IReturnVoid
     {
     {
     }
     }
 
 
     [Route("/Auth/Keys", "GET")]
     [Route("/Auth/Keys", "GET")]
+    [Authenticated(Roles = "Admin")]
     public class GetApiKeys
     public class GetApiKeys
     {
     {
     }
     }
 
 
     [Route("/Auth/Keys/{Key}", "DELETE")]
     [Route("/Auth/Keys/{Key}", "DELETE")]
+    [Authenticated(Roles = "Admin")]
     public class RevokeKey
     public class RevokeKey
     {
     {
         [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
@@ -258,6 +261,7 @@ namespace MediaBrowser.Api.Session
     }
     }
 
 
     [Route("/Auth/Keys", "POST")]
     [Route("/Auth/Keys", "POST")]
+    [Authenticated(Roles = "Admin")]
     public class CreateKey
     public class CreateKey
     {
     {
         [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]

+ 163 - 0
MediaBrowser.Api/StartupWizardService.cs

@@ -0,0 +1,163 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Connect;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
+using ServiceStack;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api
+{
+    [Route("/Startup/Complete", "POST", Summary = "Reports that the startup wizard has been completed")]
+    public class ReportStartupWizardComplete : IReturnVoid
+    {
+    }
+
+    [Route("/Startup/Info", "GET", Summary = "Gets initial server info")]
+    public class GetStartupInfo : IReturn<StartupInfo>
+    {
+    }
+
+    [Route("/Startup/Configuration", "GET", Summary = "Gets initial server configuration")]
+    public class GetStartupConfiguration : IReturn<StartupConfiguration>
+    {
+    }
+
+    [Route("/Startup/Configuration", "POST", Summary = "Updates initial server configuration")]
+    public class UpdateStartupConfiguration : StartupConfiguration, IReturnVoid
+    {
+    }
+
+    [Route("/Startup/User", "GET", Summary = "Gets initial user info")]
+    public class GetStartupUser : IReturn<StartupUser>
+    {
+    }
+
+    [Route("/Startup/User", "POST", Summary = "Updates initial user info")]
+    public class UpdateStartupUser : StartupUser, IReturn<UpdateStartupUserResult>
+    {
+    }
+
+    [Authenticated(AllowBeforeStartupWizard = true, Roles = "Admin")]
+    public class StartupWizardService : BaseApiService
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly IServerApplicationHost _appHost;
+        private readonly IUserManager _userManager;
+        private readonly IConnectManager _connectManager;
+
+        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager)
+        {
+            _config = config;
+            _appHost = appHost;
+            _userManager = userManager;
+            _connectManager = connectManager;
+        }
+
+        public void Post(ReportStartupWizardComplete request)
+        {
+            _config.Configuration.IsStartupWizardCompleted = true;
+            _config.SaveConfiguration();
+        }
+
+        public object Get(GetStartupInfo request)
+        {
+            var info = _appHost.GetSystemInfo();
+
+            return new StartupInfo
+            {
+                SupportsRunningAsService = info.SupportsRunningAsService
+            };
+        }
+
+        public object Get(GetStartupConfiguration request)
+        {
+            return new StartupConfiguration
+            {
+                UICulture = _config.Configuration.UICulture,
+                EnableInternetProviders = _config.Configuration.EnableInternetProviders,
+                SaveLocalMeta = _config.Configuration.SaveLocalMeta,
+                MetadataCountryCode = _config.Configuration.MetadataCountryCode,
+                PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
+            };
+        }
+
+        public void Post(UpdateStartupConfiguration request)
+        {
+            _config.Configuration.UICulture = request.UICulture;
+            _config.Configuration.EnableInternetProviders = request.EnableInternetProviders;
+            _config.Configuration.SaveLocalMeta = request.SaveLocalMeta;
+            _config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
+            _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
+            _config.SaveConfiguration();
+        }
+
+        public object Get(GetStartupUser request)
+        {
+            var user = _userManager.Users.First();
+
+            return new StartupUser
+            {
+                Name = user.Name,
+                ConnectUserName = user.ConnectUserName
+            };
+        }
+
+        public async Task<object> Post(UpdateStartupUser request)
+        {
+            var user = _userManager.Users.First();
+
+            // TODO: This should be handled internally by xbmc metadata
+            const string metadataKey = "xbmcmetadata";
+            var metadata = _config.GetConfiguration<XbmcMetadataOptions>(metadataKey);
+            metadata.UserId = user.Id.ToString("N");
+            _config.SaveConfiguration(metadataKey, metadata);
+
+            user.Name = request.Name;
+            await _userManager.UpdateUser(user).ConfigureAwait(false);
+
+            var result = new UpdateStartupUserResult();
+
+            if (!string.IsNullOrWhiteSpace(user.ConnectUserName) &&
+                string.IsNullOrWhiteSpace(request.ConnectUserName))
+            {
+                await _connectManager.RemoveConnect(user.Id.ToString("N")).ConfigureAwait(false);
+            }
+            else if (!string.Equals(user.ConnectUserName, request.ConnectUserName, StringComparison.OrdinalIgnoreCase))
+            {
+                result.UserLinkResult = await _connectManager.LinkUser(user.Id.ToString("N"), request.ConnectUserName).ConfigureAwait(false);
+            }
+
+            return result;
+        }
+    }
+
+    public class StartupConfiguration
+    {
+        public string UICulture { get; set; }
+        public bool EnableInternetProviders { get; set; }
+        public bool SaveLocalMeta { get; set; }
+        public string MetadataCountryCode { get; set; }
+        public string PreferredMetadataLanguage { get; set; }
+    }
+
+    public class StartupInfo
+    {
+        public bool SupportsRunningAsService { get; set; }
+    }
+
+    public class StartupUser
+    {
+        public string Name { get; set; }
+        public string ConnectUserName { get; set; }
+    }
+
+    public class UpdateStartupUserResult
+    {
+        public UserLinkResult UserLinkResult { get; set; }
+    }
+}

+ 1 - 1
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -19,7 +19,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Api.Subtitles
 namespace MediaBrowser.Api.Subtitles
 {
 {
     [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
     [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class DeleteSubtitle
     public class DeleteSubtitle
     {
     {
         /// <summary>
         /// <summary>

+ 1 - 1
MediaBrowser.Api/System/ActivityLogService.cs

@@ -29,7 +29,7 @@ namespace MediaBrowser.Api.System
         public string MinDate { get; set; }
         public string MinDate { get; set; }
     }
     }
 
 
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class ActivityLogService : BaseApiService
     public class ActivityLogService : BaseApiService
     {
     {
         private readonly IActivityManager _activityManager;
         private readonly IActivityManager _activityManager;

+ 3 - 2
MediaBrowser.Api/System/SystemService.cs

@@ -35,7 +35,7 @@ namespace MediaBrowser.Api.System
     /// Class RestartApplication
     /// Class RestartApplication
     /// </summary>
     /// </summary>
     [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
     [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class RestartApplication
     public class RestartApplication
     {
     {
     }
     }
@@ -51,7 +51,7 @@ namespace MediaBrowser.Api.System
     }
     }
 
 
     [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
     [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class GetServerLogs : IReturn<List<LogFile>>
     public class GetServerLogs : IReturn<List<LogFile>>
     {
     {
     }
     }
@@ -64,6 +64,7 @@ namespace MediaBrowser.Api.System
     }
     }
 
 
     [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")]
     [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")]
+    [Authenticated(Roles = "Admin")]
     public class GetLogFile
     public class GetLogFile
     {
     {
         [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]

+ 1 - 2
MediaBrowser.Api/TvShowsService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Api.UserLibrary;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;

+ 2 - 4
MediaBrowser.Api/UserLibrary/GameGenresService.cs

@@ -13,8 +13,7 @@ using System.Linq;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
-    [Route("/GameGenres", "GET")]
-    [Api(Description = "Gets all Game genres from a given item, folder, or the entire library")]
+    [Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")]
     public class GetGameGenres : GetItemsByName
     public class GetGameGenres : GetItemsByName
     {
     {
         public GetGameGenres()
         public GetGameGenres()
@@ -23,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary
         }
         }
     }
     }
 
 
-    [Route("/GameGenres/{Name}", "GET")]
-    [Api(Description = "Gets a Game genre, by name")]
+    [Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")]
     public class GetGameGenre : IReturn<BaseItemDto>
     public class GetGameGenre : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 4
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -16,8 +16,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetGenres
     /// Class GetGenres
     /// </summary>
     /// </summary>
-    [Route("/Genres", "GET")]
-    [Api(Description = "Gets all genres from a given item, folder, or the entire library")]
+    [Route("/Genres", "GET", Summary = "Gets all genres from a given item, folder, or the entire library")]
     public class GetGenres : GetItemsByName
     public class GetGenres : GetItemsByName
     {
     {
     }
     }
@@ -25,8 +24,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetGenre
     /// Class GetGenre
     /// </summary>
     /// </summary>
-    [Route("/Genres/{Name}", "GET")]
-    [Api(Description = "Gets a genre, by name")]
+    [Route("/Genres/{Name}", "GET", Summary = "Gets a genre, by name")]
     public class GetGenre : IReturn<BaseItemDto>
     public class GetGenre : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 4
MediaBrowser.Api/UserLibrary/MusicGenresService.cs

@@ -13,8 +13,7 @@ using System.Linq;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
-    [Route("/MusicGenres", "GET")]
-    [Api(Description = "Gets all music genres from a given item, folder, or the entire library")]
+    [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
     public class GetMusicGenres : GetItemsByName
     public class GetMusicGenres : GetItemsByName
     {
     {
         public GetMusicGenres()
         public GetMusicGenres()
@@ -23,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary
         }
         }
     }
     }
 
 
-    [Route("/MusicGenres/{Name}", "GET")]
-    [Api(Description = "Gets a music genre, by name")]
+    [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
     public class GetMusicGenre : IReturn<BaseItemDto>
     public class GetMusicGenre : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 4
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -15,8 +15,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetPersons
     /// Class GetPersons
     /// </summary>
     /// </summary>
-    [Route("/Persons", "GET")]
-    [Api(Description = "Gets all persons from a given item, folder, or the entire library")]
+    [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")]
     public class GetPersons : GetItemsByName
     public class GetPersons : GetItemsByName
     {
     {
         /// <summary>
         /// <summary>
@@ -30,8 +29,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetPerson
     /// Class GetPerson
     /// </summary>
     /// </summary>
-    [Route("/Persons/{Name}", "GET")]
-    [Api(Description = "Gets a person, by name")]
+    [Route("/Persons/{Name}", "GET", Summary = "Gets a person, by name")]
     public class GetPerson : IReturn<BaseItemDto>
     public class GetPerson : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 8 - 16
MediaBrowser.Api/UserLibrary/PlaystateService.cs

@@ -15,8 +15,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class MarkPlayedItem
     /// Class MarkPlayedItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/PlayedItems/{Id}", "POST")]
-    [Api(Description = "Marks an item as played")]
+    [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")]
     public class MarkPlayedItem : IReturn<UserItemDataDto>
     public class MarkPlayedItem : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -40,8 +39,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class MarkUnplayedItem
     /// Class MarkUnplayedItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")]
-    [Api(Description = "Marks an item as unplayed")]
+    [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")]
     public class MarkUnplayedItem : IReturn<UserItemDataDto>
     public class MarkUnplayedItem : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -59,20 +57,17 @@ namespace MediaBrowser.Api.UserLibrary
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Sessions/Playing", "POST")]
-    [Api(Description = "Reports playback has started within a session")]
+    [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")]
     public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
     public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
     {
     {
     }
     }
 
 
-    [Route("/Sessions/Playing/Progress", "POST")]
-    [Api(Description = "Reports playback progress within a session")]
+    [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")]
     public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
     public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
     {
     {
     }
     }
 
 
-    [Route("/Sessions/Playing/Stopped", "POST")]
-    [Api(Description = "Reports playback has stopped within a session")]
+    [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")]
     public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
     public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
     {
     {
     }
     }
@@ -80,8 +75,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class OnPlaybackStart
     /// Class OnPlaybackStart
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/PlayingItems/{Id}", "POST")]
-    [Api(Description = "Reports that a user has begun playing an item")]
+    [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")]
     public class OnPlaybackStart : IReturnVoid
     public class OnPlaybackStart : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -125,8 +119,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class OnPlaybackProgress
     /// Class OnPlaybackProgress
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")]
-    [Api(Description = "Reports a user's playback progress")]
+    [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")]
     public class OnPlaybackProgress : IReturnVoid
     public class OnPlaybackProgress : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -172,8 +165,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class OnPlaybackStopped
     /// Class OnPlaybackStopped
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")]
-    [Api(Description = "Reports that a user has stopped playing an item")]
+    [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")]
     public class OnPlaybackStopped : IReturnVoid
     public class OnPlaybackStopped : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>

+ 9 - 28
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
@@ -21,8 +20,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetItem
     /// Class GetItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}", "GET")]
-    [Api(Description = "Gets an item from a user's library")]
+    [Route("/Users/{UserId}/Items/{Id}", "GET", Summary = "Gets an item from a user's library")]
     public class GetItem : IReturn<BaseItemDto>
     public class GetItem : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -57,8 +55,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetItem
     /// Class GetItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/Root", "GET")]
-    [Api(Description = "Gets the root folder from a user's library")]
+    [Route("/Users/{UserId}/Items/Root", "GET", Summary = "Gets the root folder from a user's library")]
     public class GetRootFolder : IReturn<BaseItemDto>
     public class GetRootFolder : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -72,8 +69,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetIntros
     /// Class GetIntros
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}/Intros", "GET")]
-    [Api(("Gets intros to play before the main media item plays"))]
+    [Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")]
     public class GetIntros : IReturn<ItemsResult>
     public class GetIntros : IReturn<ItemsResult>
     {
     {
         /// <summary>
         /// <summary>
@@ -94,8 +90,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class MarkFavoriteItem
     /// Class MarkFavoriteItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST")]
-    [Api(Description = "Marks an item as a favorite")]
+    [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST", Summary = "Marks an item as a favorite")]
     public class MarkFavoriteItem : IReturn<UserItemDataDto>
     public class MarkFavoriteItem : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -116,8 +111,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class UnmarkFavoriteItem
     /// Class UnmarkFavoriteItem
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE")]
-    [Api(Description = "Unmarks an item as a favorite")]
+    [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE", Summary = "Unmarks an item as a favorite")]
     public class UnmarkFavoriteItem : IReturn<UserItemDataDto>
     public class UnmarkFavoriteItem : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -138,8 +132,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class ClearUserItemRating
     /// Class ClearUserItemRating
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE")]
-    [Api(Description = "Deletes a user's saved personal rating for an item")]
+    [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE", Summary = "Deletes a user's saved personal rating for an item")]
     public class DeleteUserItemRating : IReturn<UserItemDataDto>
     public class DeleteUserItemRating : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -160,8 +153,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class UpdateUserItemRating
     /// Class UpdateUserItemRating
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}/Rating", "POST")]
-    [Api(Description = "Updates a user's rating for an item")]
+    [Route("/Users/{UserId}/Items/{Id}/Rating", "POST", Summary = "Updates a user's rating for an item")]
     public class UpdateUserItemRating : IReturn<UserItemDataDto>
     public class UpdateUserItemRating : IReturn<UserItemDataDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -189,8 +181,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetLocalTrailers
     /// Class GetLocalTrailers
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET")]
-    [Api(Description = "Gets local trailers for an item")]
+    [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")]
     public class GetLocalTrailers : IReturn<List<BaseItemDto>>
     public class GetLocalTrailers : IReturn<List<BaseItemDto>>
     {
     {
         /// <summary>
         /// <summary>
@@ -211,8 +202,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// <summary>
     /// Class GetSpecialFeatures
     /// Class GetSpecialFeatures
     /// </summary>
     /// </summary>
-    [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET")]
-    [Api(Description = "Gets special features for an item")]
+    [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")]
     public class GetSpecialFeatures : IReturn<List<BaseItemDto>>
     public class GetSpecialFeatures : IReturn<List<BaseItemDto>>
     {
     {
         /// <summary>
         /// <summary>
@@ -280,15 +270,6 @@ namespace MediaBrowser.Api.UserLibrary
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
         private readonly IUserViewManager _userViewManager;
         private readonly IUserViewManager _userViewManager;
 
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UserLibraryService" /> class.
-        /// </summary>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="userDataRepository">The user data repository.</param>
-        /// <param name="dtoService">The dto service.</param>
-        /// <param name="userViewManager">The user view manager.</param>
-        /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager)
         public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager)
         {
         {
             _userManager = userManager;
             _userManager = userManager;

+ 3 - 31
MediaBrowser.Api/UserService.cs

@@ -12,7 +12,6 @@ using ServiceStack;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -59,7 +58,7 @@ namespace MediaBrowser.Api
     /// Class DeleteUser
     /// Class DeleteUser
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")]
     [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class DeleteUser : IReturnVoid
     public class DeleteUser : IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -151,25 +150,16 @@ namespace MediaBrowser.Api
     /// Class UpdateUser
     /// Class UpdateUser
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}", "POST", Summary = "Updates a user")]
     [Route("/Users/{Id}", "POST", Summary = "Updates a user")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class UpdateUser : UserDto, IReturnVoid
     public class UpdateUser : UserDto, IReturnVoid
     {
     {
     }
     }
 
 
-    /// <summary>
-    /// Class CreateUser
-    /// </summary>
-    [Route("/Users", "POST", Summary = "Creates a user")]
-    [Authenticated]
-    public class CreateUser : UserDto, IReturn<UserDto>
-    {
-    }
-
     /// <summary>
     /// <summary>
     /// Class CreateUser
     /// Class CreateUser
     /// </summary>
     /// </summary>
     [Route("/Users/New", "POST", Summary = "Creates a user")]
     [Route("/Users/New", "POST", Summary = "Creates a user")]
-    [Authenticated]
+    [Authenticated(Roles = "Admin")]
     public class CreateUserByName : IReturn<UserDto>
     public class CreateUserByName : IReturn<UserDto>
     {
     {
         [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
         [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
@@ -473,24 +463,6 @@ namespace MediaBrowser.Api
             user.UpdateConfiguration(dtoUser.Configuration);
             user.UpdateConfiguration(dtoUser.Configuration);
         }
         }
 
 
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Post(CreateUser request)
-        {
-            var dtoUser = request;
-
-            var newUser = _userManager.CreateUser(dtoUser.Name).Result;
-
-            newUser.UpdateConfiguration(dtoUser.Configuration);
-
-            var result = _userManager.GetUserDto(newUser, Request.RemoteIp);
-
-            return ToOptimizedResult(result);
-        }
-
         /// <summary>
         /// <summary>
         /// Posts the specified request.
         /// Posts the specified request.
         /// </summary>
         /// </summary>

+ 3 - 1
MediaBrowser.Api/VideosService.cs

@@ -15,6 +15,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
     [Route("/Videos/{Id}/AdditionalParts", "GET", Summary = "Gets additional parts for a video.")]
     [Route("/Videos/{Id}/AdditionalParts", "GET", Summary = "Gets additional parts for a video.")]
+    [Authenticated]
     public class GetAdditionalParts : IReturn<ItemsResult>
     public class GetAdditionalParts : IReturn<ItemsResult>
     {
     {
         [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -29,6 +30,7 @@ namespace MediaBrowser.Api
     }
     }
 
 
     [Route("/Videos/{Id}/AlternateSources", "DELETE", Summary = "Removes alternate video sources.")]
     [Route("/Videos/{Id}/AlternateSources", "DELETE", Summary = "Removes alternate video sources.")]
+    [Authenticated(Roles = "Admin")]
     public class DeleteAlternateSources : IReturnVoid
     public class DeleteAlternateSources : IReturnVoid
     {
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
@@ -36,13 +38,13 @@ namespace MediaBrowser.Api
     }
     }
 
 
     [Route("/Videos/MergeVersions", "POST", Summary = "Merges videos into a single record")]
     [Route("/Videos/MergeVersions", "POST", Summary = "Merges videos into a single record")]
+    [Authenticated(Roles = "Admin")]
     public class MergeVersions : IReturnVoid
     public class MergeVersions : IReturnVoid
     {
     {
         [ApiMember(Name = "Ids", Description = "Item id list. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         [ApiMember(Name = "Ids", Description = "Item id list. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         public string Ids { get; set; }
         public string Ids { get; set; }
     }
     }
 
 
-    [Authenticated]
     public class VideosService : BaseApiService
     public class VideosService : BaseApiService
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;

+ 1 - 1
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -1618,7 +1618,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             var parent = user.RootFolder;
             var parent = user.RootFolder;
 
 
-            list.Add(await GetUserView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, parent).ConfigureAwait(false));
 
 

+ 3 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -220,8 +220,11 @@
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IRestfulService.cs" />
     <Compile Include="Net\IRestfulService.cs" />
+    <Compile Include="Net\IServiceRequest.cs" />
     <Compile Include="Net\ISessionContext.cs" />
     <Compile Include="Net\ISessionContext.cs" />
     <Compile Include="Net\LoggedAttribute.cs" />
     <Compile Include="Net\LoggedAttribute.cs" />
+    <Compile Include="Net\SecurityException.cs" />
+    <Compile Include="Net\ServiceStackServiceRequest.cs" />
     <Compile Include="Net\StaticResultOptions.cs" />
     <Compile Include="Net\StaticResultOptions.cs" />
     <Compile Include="News\INewsService.cs" />
     <Compile Include="News\INewsService.cs" />
     <Compile Include="Notifications\INotificationManager.cs" />
     <Compile Include="Notifications\INotificationManager.cs" />

+ 12 - 3
MediaBrowser.Controller/Net/AuthenticatedAttribute.cs

@@ -5,7 +5,7 @@ using System.Linq;
 
 
 namespace MediaBrowser.Controller.Net
 namespace MediaBrowser.Controller.Net
 {
 {
-    public class AuthenticatedAttribute : Attribute, IHasRequestFilter, IAuthenticated
+    public class AuthenticatedAttribute : Attribute, IHasRequestFilter, IAuthenticationAttributes
     {
     {
         public IAuthService AuthService { get; set; }
         public IAuthService AuthService { get; set; }
 
 
@@ -21,6 +21,12 @@ namespace MediaBrowser.Controller.Net
         /// <value><c>true</c> if [escape parental control]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [escape parental control]; otherwise, <c>false</c>.</value>
         public bool EscapeParentalControl { get; set; }
         public bool EscapeParentalControl { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether [allow before startup wizard].
+        /// </summary>
+        /// <value><c>true</c> if [allow before startup wizard]; otherwise, <c>false</c>.</value>
+        public bool AllowBeforeStartupWizard { get; set; }
+        
         /// <summary>
         /// <summary>
         /// The request filter is executed before the service.
         /// The request filter is executed before the service.
         /// </summary>
         /// </summary>
@@ -29,7 +35,9 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestDto">The request DTO</param>
         /// <param name="requestDto">The request DTO</param>
         public void RequestFilter(IRequest request, IResponse response, object requestDto)
         public void RequestFilter(IRequest request, IResponse response, object requestDto)
         {
         {
-            AuthService.Authenticate(request, response, requestDto, this);
+            var serviceRequest = new ServiceStackServiceRequest(request);
+
+            AuthService.Authenticate(serviceRequest, this);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -60,9 +68,10 @@ namespace MediaBrowser.Controller.Net
         }
         }
     }
     }
 
 
-    public interface IAuthenticated
+    public interface IAuthenticationAttributes
     {
     {
         bool EscapeParentalControl { get; }
         bool EscapeParentalControl { get; }
+        bool AllowBeforeStartupWizard { get; }
 
 
         IEnumerable<string> GetRoles();
         IEnumerable<string> GetRoles();
     }
     }

+ 3 - 6
MediaBrowser.Controller/Net/IAuthService.cs

@@ -1,12 +1,9 @@
-using ServiceStack.Web;
-
+
 namespace MediaBrowser.Controller.Net
 namespace MediaBrowser.Controller.Net
 {
 {
     public interface IAuthService
     public interface IAuthService
     {
     {
-        void Authenticate(IRequest request, 
-            IResponse response, 
-            object requestDto, 
-            IAuthenticated authAttribtues);
+        void Authenticate(IServiceRequest request,
+            IAuthenticationAttributes authAttribtues);
     }
     }
 }
 }

+ 9 - 3
MediaBrowser.Controller/Net/IAuthorizationContext.cs

@@ -1,5 +1,4 @@
-using ServiceStack.Web;
-
+
 namespace MediaBrowser.Controller.Net
 namespace MediaBrowser.Controller.Net
 {
 {
     public interface IAuthorizationContext
     public interface IAuthorizationContext
@@ -9,6 +8,13 @@ namespace MediaBrowser.Controller.Net
         /// </summary>
         /// </summary>
         /// <param name="requestContext">The request context.</param>
         /// <param name="requestContext">The request context.</param>
         /// <returns>AuthorizationInfo.</returns>
         /// <returns>AuthorizationInfo.</returns>
-        AuthorizationInfo GetAuthorizationInfo(IRequest requestContext);
+        AuthorizationInfo GetAuthorizationInfo(object requestContext);
+        
+        /// <summary>
+        /// Gets the authorization information.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <returns>AuthorizationInfo.</returns>
+        AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext);
     }
     }
 }
 }

+ 15 - 0
MediaBrowser.Controller/Net/IServiceRequest.cs

@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace MediaBrowser.Controller.Net
+{
+    public interface IServiceRequest
+    {
+        object OriginalRequest { get; }
+        string RemoteIp { get; }
+        NameValueCollection Headers { get; }
+        NameValueCollection QueryString { get; }
+        IDictionary<string,object> Items { get; }
+        void AddResponseHeader(string name, string value);
+    }
+}

+ 5 - 4
MediaBrowser.Controller/Net/ISessionContext.cs

@@ -1,13 +1,14 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
-using ServiceStack.Web;
 
 
 namespace MediaBrowser.Controller.Net
 namespace MediaBrowser.Controller.Net
 {
 {
     public interface ISessionContext 
     public interface ISessionContext 
     {
     {
-        SessionInfo GetSession(IRequest requestContext);
-
-        User GetUser(IRequest requestContext);
+        SessionInfo GetSession(object requestContext);
+        User GetUser(object requestContext);
+   
+        SessionInfo GetSession(IServiceRequest requestContext);
+        User GetUser(IServiceRequest requestContext);
     }
     }
 }
 }

+ 3 - 1
MediaBrowser.Controller/Net/LoggedAttribute.cs

@@ -22,8 +22,10 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestDto">The request DTO</param>
         /// <param name="requestDto">The request DTO</param>
         public void RequestFilter(IRequest request, IResponse response, object requestDto)
         public void RequestFilter(IRequest request, IResponse response, object requestDto)
         {
         {
+            var serviceRequest = new ServiceStackServiceRequest(request);
+            
             //This code is executed before the service
             //This code is executed before the service
-            var auth = AuthorizationContext.GetAuthorizationInfo(request);
+            var auth = AuthorizationContext.GetAuthorizationInfo(serviceRequest);
 
 
             if (auth != null)
             if (auth != null)
             {
             {

+ 21 - 0
MediaBrowser.Controller/Net/SecurityException.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace MediaBrowser.Controller.Net
+{
+    public class SecurityException : Exception
+    {
+        public SecurityException(string message)
+            : base(message)
+        {
+
+        }
+
+        public SecurityExceptionType SecurityExceptionType { get; set; }
+    }
+
+    public enum SecurityExceptionType
+    {
+        Unauthenticated = 0,
+        ParentalControl = 1
+    }
+}

+ 62 - 0
MediaBrowser.Controller/Net/ServiceStackServiceRequest.cs

@@ -0,0 +1,62 @@
+using ServiceStack.Web;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace MediaBrowser.Controller.Net
+{
+    public class ServiceStackServiceRequest : IServiceRequest
+    {
+        private readonly IRequest _request;
+
+        public ServiceStackServiceRequest(IRequest request)
+        {
+            _request = request;
+        }
+
+        public object OriginalRequest
+        {
+            get { return _request; }
+        }
+
+        public string RemoteIp
+        {
+            get { return _request.RemoteIp; }
+        }
+
+        private NameValueCollection _headers;
+        public NameValueCollection Headers
+        {
+            get { return _headers ?? (_headers = Get(_request.Headers)); }
+        }
+
+        private NameValueCollection _query;
+        public NameValueCollection QueryString
+        {
+            get { return _query ?? (_query = Get(_request.QueryString)); }
+        }
+
+        private NameValueCollection Get(INameValueCollection coll)
+        {
+            var nv = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
+
+            foreach (var key in coll.AllKeys)
+            {
+                nv[key] = coll[key];
+            }
+
+            return nv;
+            //return coll.ToNameValueCollection();
+        }
+
+        public IDictionary<string, object> Items
+        {
+            get { return _request.Items; }
+        }
+
+        public void AddResponseHeader(string name, string value)
+        {
+            _request.Response.AddHeader(name, value);
+        }
+    }
+}

+ 0 - 6
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -255,12 +255,6 @@ namespace MediaBrowser.Controller.Session
         /// <returns>SessionInfo.</returns>
         /// <returns>SessionInfo.</returns>
         SessionInfo GetSession(string deviceId, string client, string version);
         SessionInfo GetSession(string deviceId, string client, string version);
 
 
-        /// <summary>
-        /// Validates the security token.
-        /// </summary>
-        /// <param name="accessToken">The access token.</param>
-        void ValidateSecurityToken(string accessToken);
-
         /// <summary>
         /// <summary>
         /// Logouts the specified access token.
         /// Logouts the specified access token.
         /// </summary>
         /// </summary>

+ 4 - 7
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -170,7 +170,7 @@ namespace MediaBrowser.Model.Configuration
         public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
         public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
         public bool FindInternetTrailers { get; set; }
         public bool FindInternetTrailers { get; set; }
 
 
-        public string[] InsecureApps6 { get; set; }
+        public string[] InsecureApps7 { get; set; }
 
 
         public bool SaveMetadataHidden { get; set; }
         public bool SaveMetadataHidden { get; set; }
 
 
@@ -222,19 +222,16 @@ namespace MediaBrowser.Model.Configuration
 
 
             PeopleMetadataOptions = new PeopleMetadataOptions();
             PeopleMetadataOptions = new PeopleMetadataOptions();
 
 
-            InsecureApps6 = new[]
+            InsecureApps7 = new[]
             {
             {
-                "Roku",
                 "Chromecast",
                 "Chromecast",
                 "iOS",
                 "iOS",
-                "Windows Phone",
-                "Windows RT",
-                "Xbmc",
                 "Unknown app",
                 "Unknown app",
                 "MediaPortal",
                 "MediaPortal",
                 "Media Portal",
                 "Media Portal",
                 "iPad",
                 "iPad",
-                "iPhone"
+                "iPhone",
+                "Roku"
             };
             };
 
 
             MetadataOptions = new[]
             MetadataOptions = new[]

+ 1 - 1
MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -91,7 +91,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 {typeof (ResourceNotFoundException), 404},
                 {typeof (ResourceNotFoundException), 404},
                 {typeof (FileNotFoundException), 404},
                 {typeof (FileNotFoundException), 404},
                 {typeof (DirectoryNotFoundException), 404},
                 {typeof (DirectoryNotFoundException), 404},
-                {typeof (Implementations.Security.AuthenticationException), 401},
+                {typeof (SecurityException), 401},
                 {typeof (UnauthorizedAccessException), 401}
                 {typeof (UnauthorizedAccessException), 401}
             };
             };
 
 

+ 85 - 73
MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -1,13 +1,11 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Connect;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using ServiceStack;
-using ServiceStack.Auth;
-using ServiceStack.Web;
+using MediaBrowser.Controller.Security;
 using System;
 using System;
-using System.Collections.Specialized;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
 namespace MediaBrowser.Server.Implementations.HttpServer.Security
 namespace MediaBrowser.Server.Implementations.HttpServer.Security
@@ -16,61 +14,43 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
     {
     {
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
 
 
-        public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager)
+        public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager)
         {
         {
             AuthorizationContext = authorizationContext;
             AuthorizationContext = authorizationContext;
             _config = config;
             _config = config;
             ConnectManager = connectManager;
             ConnectManager = connectManager;
-            SessionManager = sessionManager;
             UserManager = userManager;
             UserManager = userManager;
         }
         }
 
 
         public IUserManager UserManager { get; private set; }
         public IUserManager UserManager { get; private set; }
-        public ISessionManager SessionManager { get; private set; }
         public IAuthorizationContext AuthorizationContext { get; private set; }
         public IAuthorizationContext AuthorizationContext { get; private set; }
         public IConnectManager ConnectManager { get; private set; }
         public IConnectManager ConnectManager { get; private set; }
 
 
-        /// <summary>
-        /// Restrict authentication to a specific <see cref="IAuthProvider"/>.
-        /// For example, if this attribute should only permit access
-        /// if the user is authenticated with <see cref="BasicAuthProvider"/>,
-        /// you should set this property to <see cref="BasicAuthProvider.Name"/>.
-        /// </summary>
-        public string Provider { get; set; }
-
         /// <summary>
         /// <summary>
         /// Redirect the client to a specific URL if authentication failed.
         /// Redirect the client to a specific URL if authentication failed.
         /// If this property is null, simply `401 Unauthorized` is returned.
         /// If this property is null, simply `401 Unauthorized` is returned.
         /// </summary>
         /// </summary>
         public string HtmlRedirect { get; set; }
         public string HtmlRedirect { get; set; }
 
 
-        public void Authenticate(IRequest request,
-            IResponse response,
-            object requestDto,
-            IAuthenticated authAttribtues)
+        public void Authenticate(IServiceRequest request,
+            IAuthenticationAttributes authAttribtues)
         {
         {
-            if (HostContext.HasValidAuthSecret(request))
-                return;
-
-            //ExecuteBasic(req, res, requestDto); //first check if session is authenticated
-            //if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed)
-
-            ValidateUser(request, response, authAttribtues);
+            ValidateUser(request, authAttribtues);
         }
         }
 
 
-        private void ValidateUser(IRequest req, IResponse response, IAuthenticated authAttribtues)
+        private void ValidateUser(IServiceRequest request,
+            IAuthenticationAttributes authAttribtues)
         {
         {
             // This code is executed before the service
             // This code is executed before the service
-            var auth = AuthorizationContext.GetAuthorizationInfo(req);
+            var auth = AuthorizationContext.GetAuthorizationInfo(request);
 
 
-            if (!string.IsNullOrWhiteSpace(auth.Token) ||
-                !_config.Configuration.InsecureApps6.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            if (!IsExemptFromAuthenticationToken(auth, authAttribtues))
             {
             {
                 var valid = IsValidConnectKey(auth.Token);
                 var valid = IsValidConnectKey(auth.Token);
 
 
                 if (!valid)
                 if (!valid)
                 {
                 {
-                    SessionManager.ValidateSecurityToken(auth.Token);
+                    ValidateSecurityToken(request, auth.Token);
                 }
                 }
             }
             }
 
 
@@ -80,45 +60,83 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
 
 
             if (user == null & !string.IsNullOrWhiteSpace(auth.UserId))
             if (user == null & !string.IsNullOrWhiteSpace(auth.UserId))
             {
             {
-                throw new ArgumentException("User with Id " + auth.UserId + " not found");
+                throw new SecurityException("User with Id " + auth.UserId + " not found");
             }
             }
 
 
             if (user != null)
             if (user != null)
             {
             {
                 if (user.Configuration.IsDisabled)
                 if (user.Configuration.IsDisabled)
                 {
                 {
-                    throw new AuthenticationException("User account has been disabled.");
+                    throw new SecurityException("User account has been disabled.")
+                    {
+                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
+                    };
                 }
                 }
 
 
                 if (!user.Configuration.IsAdministrator &&
                 if (!user.Configuration.IsAdministrator &&
                     !authAttribtues.EscapeParentalControl &&
                     !authAttribtues.EscapeParentalControl &&
                     !user.IsParentalScheduleAllowed())
                     !user.IsParentalScheduleAllowed())
                 {
                 {
-                    response.AddHeader("X-Application-Error-Code", "ParentalControl");
-                    throw new AuthenticationException("This user account is not allowed access at this time.");
+                    request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
+                    throw new SecurityException("This user account is not allowed access at this time.")
+                    {
+                        SecurityExceptionType = SecurityExceptionType.ParentalControl
+                    };
                 }
                 }
             }
             }
 
 
-            var roles = authAttribtues.GetRoles().ToList();
+            if (!IsExemptFromRoles(auth, authAttribtues))
+            {
+                var roles = authAttribtues.GetRoles().ToList();
+
+                ValidateRoles(roles, user);
+            }
+        }
+
+        private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
+        {
+            if (!_config.Configuration.IsStartupWizardCompleted &&
+                authAttribtues.AllowBeforeStartupWizard)
+            {
+                return true;
+            }
+
+            return _config.Configuration.InsecureApps7.Contains(auth.Client ?? string.Empty,
+                StringComparer.OrdinalIgnoreCase);
+        }
+
+        private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
+        {
+            if (!_config.Configuration.IsStartupWizardCompleted &&
+                authAttribtues.AllowBeforeStartupWizard)
+            {
+                return true;
+            }
+
+            return false;
+        }
 
 
+        private void ValidateRoles(List<string> roles, User user)
+        {
             if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
             if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
             {
             {
                 if (user == null || !user.Configuration.IsAdministrator)
                 if (user == null || !user.Configuration.IsAdministrator)
                 {
                 {
-                    throw new ArgumentException("Administrative access is required for this request.");
+                    throw new SecurityException("User does not have admin access.")
+                    {
+                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
+                    };
                 }
                 }
             }
             }
-
-            if (!string.IsNullOrWhiteSpace(auth.DeviceId) &&
-                !string.IsNullOrWhiteSpace(auth.Client) &&
-                !string.IsNullOrWhiteSpace(auth.Device))
+            if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
             {
             {
-                SessionManager.LogSessionActivity(auth.Client,
-                    auth.Version,
-                    auth.DeviceId,
-                    auth.Device,
-                    req.RemoteIp,
-                    user);
+                if (user == null || !user.Configuration.EnableContentDeletion)
+                {
+                    throw new SecurityException("User does not have delete access.")
+                    {
+                        SecurityExceptionType = SecurityExceptionType.Unauthenticated
+                    };
+                }
             }
             }
         }
         }
 
 
@@ -132,40 +150,34 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
             return ConnectManager.IsAuthorizationTokenValid(token);
             return ConnectManager.IsAuthorizationTokenValid(token);
         }
         }
 
 
-        protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false)
+        private void ValidateSecurityToken(IServiceRequest request, string token)
         {
         {
-            var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect;
-            if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
+            if (string.IsNullOrWhiteSpace(token))
             {
             {
-                DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam);
-                return true;
+                throw new SecurityException("Access token is invalid or expired.");
             }
             }
-            return false;
-        }
 
 
-        public static void DoHtmlRedirect(string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam)
-        {
-            var url = req.ResolveAbsoluteUrl(redirectUrl);
-            if (includeRedirectParam)
+            var info = (AuthenticationInfo)request.Items["OriginalAuthenticationInfo"];
+
+            if (info == null)
             {
             {
-                var absoluteRequestPath = req.ResolveAbsoluteUrl("~" + req.PathInfo + ToQueryString(req.QueryString));
-                url = url.AddQueryParam(HostContext.ResolveLocalizedString(LocalizedStrings.Redirect), absoluteRequestPath);
+                throw new SecurityException("Access token is invalid or expired.");
             }
             }
 
 
-            res.RedirectToUrl(url);
-        }
-
-        private static string ToQueryString(INameValueCollection queryStringCollection)
-        {
-            return ToQueryString((NameValueCollection)queryStringCollection.Original);
-        }
+            if (!info.IsActive)
+            {
+                throw new SecurityException("Access token has expired.");
+            }
 
 
-        private static string ToQueryString(NameValueCollection queryStringCollection)
-        {
-            if (queryStringCollection == null || queryStringCollection.Count == 0)
-                return String.Empty;
+            //if (!string.IsNullOrWhiteSpace(info.UserId))
+            //{
+            //    var user = _userManager.GetUserById(info.UserId);
 
 
-            return "?" + queryStringCollection.ToFormUrlEncoded();
+            //    if (user == null || user.Configuration.IsDisabled)
+            //    {
+            //        throw new SecurityException("User account has been disabled.");
+            //    }
+            //}
         }
         }
     }
     }
 }
 }

+ 50 - 7
MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -1,14 +1,35 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Security;
 using ServiceStack.Web;
 using ServiceStack.Web;
+using System;
+using System.Collections.Generic;
+using System.Linq;
 
 
 namespace MediaBrowser.Server.Implementations.HttpServer.Security
 namespace MediaBrowser.Server.Implementations.HttpServer.Security
 {
 {
     public class AuthorizationContext : IAuthorizationContext
     public class AuthorizationContext : IAuthorizationContext
     {
     {
-        public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext)
+        private readonly IAuthenticationRepository _authRepo;
+
+        public AuthorizationContext(IAuthenticationRepository authRepo)
+        {
+            _authRepo = authRepo;
+        }
+
+        public AuthorizationInfo GetAuthorizationInfo(object requestContext)
         {
         {
+            var req = new ServiceStackServiceRequest((IRequest)requestContext);
+            return GetAuthorizationInfo(req);
+        }
+
+        public AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext)
+        {
+            object cached;
+            if (requestContext.Items.TryGetValue("AuthorizationInfo", out cached))
+            {
+                return (AuthorizationInfo)cached;
+            }
+
             return GetAuthorization(requestContext);
             return GetAuthorization(requestContext);
         }
         }
 
 
@@ -17,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
         /// </summary>
         /// </summary>
         /// <param name="httpReq">The HTTP req.</param>
         /// <param name="httpReq">The HTTP req.</param>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private AuthorizationInfo GetAuthorization(IRequest httpReq)
+        private AuthorizationInfo GetAuthorization(IServiceRequest httpReq)
         {
         {
             var auth = GetAuthorizationDictionary(httpReq);
             var auth = GetAuthorizationDictionary(httpReq);
 
 
@@ -29,7 +50,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
 
 
             if (auth != null)
             if (auth != null)
             {
             {
+                // TODO: Remove this 
                 auth.TryGetValue("UserId", out userId);
                 auth.TryGetValue("UserId", out userId);
+
                 auth.TryGetValue("DeviceId", out deviceId);
                 auth.TryGetValue("DeviceId", out deviceId);
                 auth.TryGetValue("Device", out device);
                 auth.TryGetValue("Device", out device);
                 auth.TryGetValue("Client", out client);
                 auth.TryGetValue("Client", out client);
@@ -84,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
                 }
                 }
             }
             }
 
 
-            return new AuthorizationInfo
+            var info = new AuthorizationInfo
             {
             {
                 Client = client,
                 Client = client,
                 Device = device,
                 Device = device,
@@ -93,6 +116,26 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
                 Version = version,
                 Version = version,
                 Token = token
                 Token = token
             };
             };
+
+            if (!string.IsNullOrWhiteSpace(token))
+            {
+                var result = _authRepo.Get(new AuthenticationInfoQuery
+                {
+                    AccessToken = token
+                });
+
+                var tokenInfo = result.Items.FirstOrDefault();
+
+                if (tokenInfo != null)
+                {
+                    info.UserId = tokenInfo.UserId;
+                }
+                httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
+            }
+
+            httpReq.Items["AuthorizationInfo"] = info;
+
+            return info;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -100,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
         /// </summary>
         /// </summary>
         /// <param name="httpReq">The HTTP req.</param>
         /// <param name="httpReq">The HTTP req.</param>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq)
+        private Dictionary<string, string> GetAuthorizationDictionary(IServiceRequest httpReq)
         {
         {
             var auth = httpReq.Headers["Authorization"];
             var auth = httpReq.Headers["Authorization"];
 
 

+ 14 - 2
MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs

@@ -19,18 +19,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
             _sessionManager = sessionManager;
             _sessionManager = sessionManager;
         }
         }
 
 
-        public SessionInfo GetSession(IRequest requestContext)
+        public SessionInfo GetSession(IServiceRequest requestContext)
         {
         {
             var authorization = _authContext.GetAuthorizationInfo(requestContext);
             var authorization = _authContext.GetAuthorizationInfo(requestContext);
 
 
             return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version);
             return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version);
         }
         }
 
 
-        public User GetUser(IRequest requestContext)
+        public User GetUser(IServiceRequest requestContext)
         {
         {
             var session = GetSession(requestContext);
             var session = GetSession(requestContext);
 
 
             return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value);
             return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value);
         }
         }
+
+        public SessionInfo GetSession(object requestContext)
+        {
+            var req = new ServiceStackServiceRequest((IRequest)requestContext);
+            return GetSession(req);
+        }
+
+        public User GetUser(object requestContext)
+        {
+            var req = new ServiceStackServiceRequest((IRequest)requestContext);
+            return GetUser(req);
+        }
     }
     }
 }
 }

+ 3 - 2
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
@@ -180,12 +181,12 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             if (user == null)
             if (user == null)
             {
             {
-                throw new AuthenticationException("Invalid username or password entered.");
+                throw new SecurityException("Invalid username or password entered.");
             }
             }
 
 
             if (user.Configuration.IsDisabled)
             if (user.Configuration.IsDisabled)
             {
             {
-                throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
+                throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
             }
             }
 
 
             var success = false;
             var success = false;

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

@@ -247,7 +247,6 @@
     <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" />
     <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" />
     <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
     <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
     <Compile Include="ScheduledTasks\SystemUpdateTask.cs" />
     <Compile Include="ScheduledTasks\SystemUpdateTask.cs" />
-    <Compile Include="Security\AuthenticationException.cs" />
     <Compile Include="Security\AuthenticationRepository.cs" />
     <Compile Include="Security\AuthenticationRepository.cs" />
     <Compile Include="Security\EncryptionManager.cs" />
     <Compile Include="Security\EncryptionManager.cs" />
     <Compile Include="ServerApplicationPaths.cs" />
     <Compile Include="ServerApplicationPaths.cs" />

+ 0 - 16
MediaBrowser.Server.Implementations/Security/AuthenticationException.cs

@@ -1,16 +0,0 @@
-using System;
-
-namespace MediaBrowser.Server.Implementations.Security
-{
-    public class AuthenticationException : Exception
-    {
-        public AuthenticationException(string message)
-            : base(message)
-        {
-        }
-
-        public AuthenticationException()
-        {
-        }
-    }
-}

+ 2 - 46
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -11,10 +11,10 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Connect;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
@@ -23,7 +23,6 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Users;
 using MediaBrowser.Model.Users;
-using MediaBrowser.Server.Implementations.Security;
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -253,11 +252,6 @@ namespace MediaBrowser.Server.Implementations.Session
                 throw new ArgumentNullException("deviceName");
                 throw new ArgumentNullException("deviceName");
             }
             }
 
 
-            if (user != null && user.Configuration.IsDisabled)
-            {
-                throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
-            }
-
             var activityDate = DateTime.UtcNow;
             var activityDate = DateTime.UtcNow;
 
 
             var userId = user == null ? (Guid?)null : user.Id;
             var userId = user == null ? (Guid?)null : user.Id;
@@ -1261,49 +1255,11 @@ namespace MediaBrowser.Server.Implementations.Session
             }
             }
         }
         }
 
 
-        public void ValidateSecurityToken(string token)
-        {
-            if (string.IsNullOrWhiteSpace(token))
-            {
-                throw new AuthenticationException();
-            }
-
-            var result = _authRepo.Get(new AuthenticationInfoQuery
-            {
-                AccessToken = token
-            });
-
-            var info = result.Items.FirstOrDefault();
-
-            if (info == null)
-            {
-                throw new AuthenticationException();
-            }
-
-            if (!info.IsActive)
-            {
-                throw new AuthenticationException("Access token has expired.");
-            }
-
-            if (!string.IsNullOrWhiteSpace(info.UserId))
-            {
-                var user = _userManager.GetUserById(info.UserId);
-
-                if (user == null || user.Configuration.IsDisabled)
-                {
-                    throw new AuthenticationException("User account has been disabled.");
-                }
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Authenticates the new session.
         /// Authenticates the new session.
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>Task{SessionInfo}.</returns>
         /// <returns>Task{SessionInfo}.</returns>
-        /// <exception cref="AuthenticationException">Invalid user or password entered.</exception>
-        /// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception>
-        /// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</exception>
         public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
         public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
         {
         {
             var user = _userManager.Users
             var user = _userManager.Users
@@ -1315,7 +1271,7 @@ namespace MediaBrowser.Server.Implementations.Session
             {
             {
                 EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
                 EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
 
 
-                throw new AuthenticationException("Invalid user or password entered.");
+                throw new UnauthorizedAccessException("Invalid user or password entered.");
             }
             }
 
 
             var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false);
             var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false);

+ 4 - 2
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -496,10 +496,10 @@ namespace MediaBrowser.Server.Startup.Common
             RegisterSingleInstance(activityLogRepo);
             RegisterSingleInstance(activityLogRepo);
             RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
             RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
 
 
-            var authContext = new AuthorizationContext();
+            var authContext = new AuthorizationContext(AuthenticationRepository);
             RegisterSingleInstance<IAuthorizationContext>(authContext);
             RegisterSingleInstance<IAuthorizationContext>(authContext);
             RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
             RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
-            RegisterSingleInstance<IAuthService>(new AuthService(UserManager, SessionManager, authContext, ServerConfigurationManager, ConnectManager));
+            RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager));
 
 
             RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer));
             RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer));
 
 
@@ -534,6 +534,8 @@ namespace MediaBrowser.Server.Startup.Common
             var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager)
             var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager)
                 .GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false);
                 .GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false);
 
 
+            new FFmpegValidator(Logger, ApplicationPaths).Validate(info);
+
             MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), JsonSerializer, info.EncoderPath, info.ProbePath, info.Version);
             MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), JsonSerializer, info.EncoderPath, info.ProbePath, info.Version);
             RegisterSingleInstance(MediaEncoder);
             RegisterSingleInstance(MediaEncoder);
         }
         }

+ 116 - 0
MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs

@@ -0,0 +1,116 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+namespace MediaBrowser.Server.Startup.Common.FFMpeg
+{
+    public class FFmpegValidator
+    {
+        private readonly ILogger _logger;
+        private readonly IApplicationPaths _appPaths;
+
+        public FFmpegValidator(ILogger logger, IApplicationPaths appPaths)
+        {
+            _logger = logger;
+            _appPaths = appPaths;
+        }
+
+        public void Validate(FFMpegInfo info)
+        {
+            _logger.Info("FFMpeg: {0}", info.EncoderPath);
+            _logger.Info("FFProbe: {0}", info.ProbePath);
+
+            var fileInfo = new FileInfo(info.EncoderPath);
+            var cachePath = Path.Combine(_appPaths.CachePath, fileInfo.Length.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"));
+
+            if (!File.Exists(cachePath))
+            {
+                ValidateCodecs(info.EncoderPath);
+
+                Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
+                File.WriteAllText(cachePath, string.Empty, Encoding.UTF8);
+            }
+        }
+
+        private void ValidateCodecs(string path)
+        {
+            var output = GetOutput(path, "-encoders");
+
+            var required = new[]
+            {
+                "libx264",
+                "mpeg4",
+                "msmpeg4",
+                "libvpx",
+                //"libvpx-vp9",
+                "aac",
+                "ac3",
+                "libmp3lame",
+                "libvorbis",
+                "srt"
+            };
+
+            foreach (var encoder in required)
+            {
+                var srch = " " + encoder + "  ";
+
+                if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) == -1)
+                {
+                    throw new ArgumentException("ffmpeg is missing encoder " + encoder);
+                }
+            }
+        }
+
+        private string GetOutput(string path, string arguments)
+        {
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = path,
+                    Arguments = arguments,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false,
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true
+                }
+            };
+
+            using (process)
+            {
+                process.Start();
+
+                try
+                {
+                    process.BeginErrorReadLine();
+
+                    using (var reader = new StreamReader(process.StandardOutput.BaseStream))
+                    {
+                        return reader.ReadToEnd();
+                    }
+                }
+                catch
+                {
+                    // Hate having to do this
+                    try
+                    {
+                        process.Kill();
+                    }
+                    catch (Exception ex1)
+                    {
+                        _logger.ErrorException("Error killing ffmpeg", ex1);
+                    }
+
+                    throw;
+                }
+            }
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -59,6 +59,7 @@
     <Compile Include="FFMpeg\FFMpegDownloader.cs" />
     <Compile Include="FFMpeg\FFMpegDownloader.cs" />
     <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
     <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
     <Compile Include="FFMpeg\FFMpegInfo.cs" />
     <Compile Include="FFMpeg\FFMpegInfo.cs" />
+    <Compile Include="FFMpeg\FFmpegValidator.cs" />
     <Compile Include="INativeApp.cs" />
     <Compile Include="INativeApp.cs" />
     <Compile Include="Migrations\DeprecatePlugins.cs" />
     <Compile Include="Migrations\DeprecatePlugins.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />

+ 1 - 2
MediaBrowser.ServerApplication/MainStartup.cs

@@ -195,8 +195,7 @@ namespace MediaBrowser.ServerApplication
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         private static void BeginLog(ILogger logger, IApplicationPaths appPaths)
         private static void BeginLog(ILogger logger, IApplicationPaths appPaths)
         {
         {
-            logger.Info("Media Browser Server started");
-            ApplicationHost.LogEnvironmentInfo(logger, appPaths);
+            ApplicationHost.LogEnvironmentInfo(logger, appPaths, true);
         }
         }
 
 
         private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>();
         private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>();