Skip to content
This repository has been archived by the owner on Apr 6, 2021. It is now read-only.

Add option for pre 5.3 server authentication #122

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Classes/Data Model/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class Server {
static let testServerId = Int64.max
static var testServer: Server {
// Return model directly rather than storing in the database
let testServer = Server(serverId: self.testServerId, type: .subsonic, url: "https://isubapp.com:9002", username: "isub-guest", basicAuth: false)
let testServer = Server(serverId: self.testServerId, type: .subsonic, url: "https://isubapp.com:9002", username: "isub-guest", basicAuth: false, legacyAuth: false)
testServer.password = "1sub1snumb3r0n3"
return testServer
}
Expand All @@ -39,6 +39,7 @@ final class Server {
var url: String
var username: String
var basicAuth: Bool
var legacyAuth: Bool

fileprivate var passwordKey: String { return "\(serverId) - \(username)" }
var password: String? {
Expand All @@ -54,12 +55,13 @@ final class Server {
}
}

init(serverId: Int64, type: ServerType, url: String, username: String, basicAuth: Bool, repository: ServerRepository = ServerRepository.si) {
init(serverId: Int64, type: ServerType, url: String, username: String, basicAuth: Bool, legacyAuth: Bool, repository: ServerRepository = ServerRepository.si) {
self.serverId = serverId
self.type = type
self.url = url
self.username = username
self.basicAuth = basicAuth
self.repository = repository
self.legacyAuth = legacyAuth
}
}
11 changes: 6 additions & 5 deletions Classes/Data Model/ServerRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ struct ServerRepository: ItemRepository {
}

// Save new server
func server(type: ServerType, url: String, username: String, password: String, basicAuth: Bool) -> Server? {
func server(type: ServerType, url: String, username: String, password: String, basicAuth: Bool, legacyAuth: Bool) -> Server? {
var success = true
var serverId: Int64 = -1
Database.si.write.inDatabase { db in
do {
let query = "INSERT INTO servers VALUES (?, ?, ?, ?, ?)"
try db.executeUpdate(query, NSNull(), type.rawValue, url, username, basicAuth)
let query = "INSERT INTO servers VALUES (?, ?, ?, ?, ?, ?)"
try db.executeUpdate(query, NSNull(), type.rawValue, url, username, basicAuth, legacyAuth)
serverId = db.lastInsertRowId()
} catch {
printError(error)
Expand All @@ -66,7 +66,7 @@ struct ServerRepository: ItemRepository {
}

if success && serverId >= 0 {
let server = Server(serverId: serverId, type: type, url: url, username: username, basicAuth: basicAuth)
let server = Server(serverId: serverId, type: type, url: url, username: username, basicAuth: basicAuth, legacyAuth: legacyAuth)
server.password = password
return server
}
Expand All @@ -81,9 +81,10 @@ extension Server: PersistedItem {
let url = result.string(forColumnIndex: 2) ?? ""
let username = result.string(forColumnIndex: 3) ?? ""
let basicAuth = result.bool(forColumnIndex: 4)
let legacyAuth = result.bool(forColumnIndex: 5)
let repository = repository as! ServerRepository

self.init(serverId: serverId, type: type, url: url, username: username, basicAuth: basicAuth, repository: repository)
self.init(serverId: serverId, type: type, url: url, username: username, basicAuth: basicAuth, legacyAuth: legacyAuth, repository: repository)
}

class func item(itemId: Int64, serverId: Int64, repository: ItemRepository = ServerRepository.si) -> Item? {
Expand Down
2 changes: 2 additions & 0 deletions Classes/Extensions/NSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum iSubErrorCode: Int {
case subsonicTrialExpired = 6
case requiresBasicAuth = 7
case serverNotInDb = 8
case tokenAuthNotSupported = 9

var description: String {
switch self {
Expand All @@ -30,6 +31,7 @@ enum iSubErrorCode: Int {
case .subsonicTrialExpired: return "Subsonic API Trial Expired"
case .requiresBasicAuth: return "Requires HTTP Basic Authentication"
case .serverNotInDb: return "The server does not exist in the database"
case .tokenAuthNotSupported: return "Server does not support token authentication"
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions Classes/Server Loading/Loaders/New Model/StatusLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,24 @@ final class StatusLoader: ApiLoader {
fileprivate(set) var username: String
fileprivate(set) var password: String
fileprivate(set) var basicAuth: Bool
fileprivate(set) var legacyAuth: Bool

fileprivate(set) var versionString: String?
fileprivate(set) var majorVersion: Int?
fileprivate(set) var minorVersion: Int?

convenience init(server: Server) {
let password = server.password ?? ""
self.init(url: server.url, username: server.username, password: password, basicAuth: server.basicAuth)
self.init(url: server.url, username: server.username, password: password, legacyAuth: server.legacyAuth, basicAuth: server.basicAuth)
self.server = server
}

init(url: String, username: String, password: String, basicAuth: Bool = false, serverId: Int64? = nil) {
init(url: String, username: String, password: String, legacyAuth: Bool, basicAuth: Bool = false, serverId: Int64? = nil) {
self.url = url
self.username = username
self.password = password
self.basicAuth = basicAuth
self.legacyAuth = legacyAuth
if let serverId = serverId {
super.init(serverId: serverId)
} else {
Expand All @@ -40,7 +42,7 @@ final class StatusLoader: ApiLoader {
}

override func createRequest() -> URLRequest? {
return URLRequest(subsonicAction: .ping, baseUrl: url, username: username, password: password, basicAuth: basicAuth)
return URLRequest(subsonicAction: .ping, baseUrl: url, username: username, password: password, basicAuth: basicAuth, legacyAuth: legacyAuth)
}

override func processResponse(root: RXMLElement) -> Bool {
Expand Down Expand Up @@ -76,6 +78,10 @@ final class StatusLoader: ApiLoader {
// Subsonic trial ended
super.failed(error: NSError(iSubCode: .subsonicTrialExpired))
return
} else if error.code == 41 && !self.legacyAuth {
// Token auth not supported, we should probably try legacy
super.failed(error: NSError(iSubCode: .tokenAuthNotSupported))
return
}
}

Expand Down
35 changes: 24 additions & 11 deletions Classes/Server Loading/SubsonicURLRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,39 @@ extension URLRequest {
parameters: parameters,
fragment: fragment,
byteOffset: byteOffset,
basicAuth: server.basicAuth)
basicAuth: server.basicAuth,
legacyAuth: server.legacyAuth)
} else {
return nil
}
}

init(subsonicAction: SubsonicURLAction, baseUrl: String, username: String, password: String, parameters: [String: Any]? = nil, fragment: String? = nil, byteOffset: Int = 0, basicAuth: Bool = false) {
init(subsonicAction: SubsonicURLAction, baseUrl: String, username: String, password: String, parameters: [String: Any]? = nil, fragment: String? = nil, byteOffset: Int = 0, basicAuth: Bool = false, legacyAuth: Bool = false) {

var urlString = "\(baseUrl)/rest/\(subsonicAction.rawValue).\(subsonicAction.urlExtension)"

// Generate a 32 character random salt
// Then use the Subsonic required md5(password + salt) function to generate the token.
let salt = String.random(32)
let token = (password + salt).md5.lowercased()

// Only support Subsonic version 5.3 and later
let version = "1.13.0"

var parametersString = "?c=iSub"
if !legacyAuth {
// Generate a 32 character random salt
// Then use the Subsonic required md5(password + salt) function to generate the token.
let salt = String.random(32)
let token = (password + salt).md5.lowercased()

// Supports Subsonic version 5.3 and later
let version = "1.13.0"
parametersString += "&v=\(version)&u=\(username)&t=\(token)&s=\(salt)"
} else {
var encpass = "enc:"
for scalar in password.unicodeScalars {
encpass += String(scalar.value, radix: 16)
}
// For Subsonic version prior to 5.3 or other vendors like Ampache
let version = "1.11"
parametersString += "&v=\(version)&u=\(username)&p=\(encpass)"
}


// Setup the parameters
var parametersString = "?c=iSub&v=\(version)&u=\(username)&t=\(token)&s=\(salt)"
if let parameters = parameters {
for (key, value) in parameters {
if let value = value as? [Any] {
Expand Down
3 changes: 3 additions & 0 deletions Classes/Singletons/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ final class Database {
if !db.columnExists("basicAuth", inTableWithName: "servers") {
try? db.executeUpdate("ALTER TABLE servers ADD COLUMN basicAuth INTEGER")
}
if !db.columnExists("legacyAuth", inTableWithName: "servers") {
try? db.executeUpdate("ALTER TABLE servers ADD COLUMN legacyAuth INTEGER")
}
}

// Create the default playlist tables
Expand Down
7 changes: 4 additions & 3 deletions Classes/UI/Specific/SubsonicServerEditViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class SubsonicServerEditViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var urlField: UITextField!
@IBOutlet weak var usernameField: UITextField!
@IBOutlet weak var passwordField: UITextField!
@IBOutlet weak var legacyAuthField: UISwitch!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var saveButton: UIButton!

Expand Down Expand Up @@ -148,7 +149,7 @@ class SubsonicServerEditViewController: UIViewController, UITextFieldDelegate {
}

LoadingScreen.show(withMessage: "Checking Server")
statusLoader = StatusLoader(url: urlField.text!, username: usernameField.text!, password: passwordField.text!)
statusLoader = StatusLoader(url: urlField.text!, username: usernameField.text!, password: passwordField.text!, legacyAuth: legacyAuthField.isOn)
statusLoader!.completionHandler = loadingCompletionHandler
statusLoader!.start()
}
Expand All @@ -169,7 +170,7 @@ class SubsonicServerEditViewController: UIViewController, UITextFieldDelegate {
server.replace()
} else {
// Create new server
server = ServerRepository.si.server(type: .subsonic, url: statusLoader.url, username: statusLoader.username, password: statusLoader.password, basicAuth: statusLoader.basicAuth)
server = ServerRepository.si.server(type: .subsonic, url: statusLoader.url, username: statusLoader.username, password: statusLoader.password, basicAuth: statusLoader.basicAuth, legacyAuth: statusLoader.legacyAuth)
}

delegate?.serverEdited(server!)
Expand All @@ -183,7 +184,7 @@ class SubsonicServerEditViewController: UIViewController, UITextFieldDelegate {
}

// Try again with basic auth (or without if the server already had it)
self.statusLoader = StatusLoader(url: urlField.text!, username: usernameField.text!, password: passwordField.text!, basicAuth: basicAuth)
self.statusLoader = StatusLoader(url: urlField.text!, username: usernameField.text!, password: passwordField.text!, legacyAuth: false, basicAuth: basicAuth)
self.statusLoader!.completionHandler = loadingCompletionHandler
self.statusLoader!.start()
retriedStatusLoader = true
Expand Down
74 changes: 44 additions & 30 deletions Classes/en.lproj/SubsonicServerEditViewController.xib
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<customFonts key="customFonts">
<array key="Helvetica.ttc">
<string>Helvetica</string>
<string>Helvetica-Bold</string>
</array>
</customFonts>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SubsonicServerEditViewController">
<connections>
<outlet property="cancelButton" destination="17" id="18"/>
<outlet property="legacyAuthField" destination="ces-hD-MAe" id="ug0-pO-Dd5"/>
<outlet property="passwordField" destination="5" id="10"/>
<outlet property="saveButton" destination="7" id="11"/>
<outlet property="urlField" destination="6" id="8"/>
Expand Down Expand Up @@ -54,29 +62,6 @@
<outlet property="delegate" destination="-1" id="16"/>
</connections>
</textField>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="7">
<rect key="frame" x="200" y="247" width="75" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="18"/>
<state key="normal" title="Save">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="saveButtonPressed:" destination="-1" eventType="touchUpInside" id="12"/>
</connections>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="17">
<rect key="frame" x="104" y="247" width="75" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="18"/>
<state key="normal" title="Cancel">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="cancelButtonPressed:" destination="-1" eventType="touchUpInside" id="19"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="http://myserver.subsonic.org" lineBreakMode="tailTruncation" minimumFontSize="10" id="20">
<rect key="frame" x="49" y="27" width="235" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
Expand All @@ -98,6 +83,40 @@
<color key="textColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="ces-hD-MAe">
<rect key="frame" x="49" y="267" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="legacy authentication" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="fvn-fA-kSS">
<rect key="frame" x="49" y="240" width="164" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.51251485233479832" green="0.51251485233479832" blue="0.51251485233479832" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="7">
<rect key="frame" x="171" y="316" width="75" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="18"/>
<state key="normal" title="Save">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="saveButtonPressed:" destination="-1" eventType="touchUpInside" id="12"/>
</connections>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="17">
<rect key="frame" x="75" y="316" width="75" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="18"/>
<state key="normal" title="Cancel">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="cancelButtonPressed:" destination="-1" eventType="touchUpInside" id="19"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
Expand All @@ -107,9 +126,4 @@
<resources>
<image name="settings-page.png" width="320" height="460"/>
</resources>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination" type="retina4_7.fullscreen"/>
</simulatedMetricsContainer>
</document>
Loading