[GH-ISSUE #156] Update twitter status,authorization problem #95

Closed
opened 2026-03-03 16:45:37 +03:00 by kerem · 5 comments
Owner

Originally created by @ohcrfpv on GitHub (Dec 13, 2015).
Original GitHub issue: https://github.com/OAuthSwift/OAuthSwift/issues/156

OAuthSwift version is 0.4.8
Twitter API is https://api.twitter.com/1.1/statuses/update.json,
you can visit https://dev.twitter.com/rest/reference/post/statuses/update for more detail.

When my parameters has in_reply_to_status_id,it return like below

{
  "errors" : [
    {
      "message" : "Could not authenticate you.",
      "code" : 32
    }
  ]
}

But only send status is ok, it is pretty odd.
My project use https://github.com/Alamofire/Alamofire to request the Twitter REST API.
I write a method to fetch header,do not forget replace the oauth_token,oauth_token_secret and Config,for some personal reason,i don't want to expose it:

private static func buildHeaders(requestMethod: OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> [String : String] {
        let oauth_token = "" //replace it,for some personal reason,i don't want to expose it
        let oauth_token_secret = "" //replace it,for some personal reason,i don't want to expose it

        let c = OAuthSwiftClient(consumerKey: Config.consumerKey, consumerSecret: Config.consumerSecret, accessToken: oauth_token, accessTokenSecret: oauth_token_secret)
        let credential = c.credential

        let authorization = credential.authorizationHeaderForMethod(requestMethod,
            url: url,
            parameters: parameters)

        let headers = ["Authorization": authorization]
        return headers
    }

and set the header

private static func setHeaders(mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> NSMutableURLRequest {
        let headers = Router.buildHeaders(oauthMethod, url:url, parameters: parameters)
        for (field, value) in headers {
            mutableURLRequest.setValue(value, forHTTPHeaderField: field)
        }
        return mutableURLRequest
    }

The class is:

//
//  Router.swift
//  Twicrow
//
//  Created by fewspider on 15/9/28.
//  Copyright © 2015年 fff. All rights reserved.
//

import Foundation
import Alamofire
import SwiftyJSON
import OAuthSwift

struct Config {
    static let consumerKey = "" // replace it
    static let consumerSecret = "" // replace it
    static let callbackURL = "oauth-swift://oauth-callback/twitter"
}

enum Router: URLRequestConvertible {
    static let baseURLString = "https://api.twitter.com/1.1"

    case ReadHomeTimeline([String: AnyObject])
    case ReadUserTimeline([String: AnyObject])

    case UpdateStatus([String: AnyObject])
    case Retweet(Int)

    var method: Alamofire.Method {
        switch self {
        case .UpdateStatus, .Retweet:
            return .POST
        default:
            return .GET
        }
    }

    var oauthMethod: OAuthSwiftHTTPRequest.Method {
        switch self {
        case .UpdateStatus, .Retweet:
            return .POST
        default:
            return .GET
        }
    }

    var path: String {
        switch self {
        case ReadHomeTimeline(_):
            return "/statuses/home_timeline.json"
        case ReadUserTimeline(_):
            return "/statuses/user_timeline.json"
        case UpdateStatus(_):
            return "/statuses/update.json"
        case .Retweet(let id):
            return "/statuses/retweet/\(id).json"
        }
    }

    // MARK: URLRequestConvertible

    var URLRequest: NSMutableURLRequest {
        let URL = NSURL(string: Router.baseURLString)!
        let absUrl = URL.URLByAppendingPathComponent(path)
        let mutableURLRequest = NSMutableURLRequest(URL: absUrl)
        mutableURLRequest.HTTPMethod = method.rawValue


        switch self {
        case .UpdateStatus(let parameters):
            return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters)
        case .ReadHomeTimeline(let parameters):
            return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters)
        case .ReadUserTimeline(let parameters):
            return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters)
        default:
            let parameters = [String: AnyObject]()
            Router.setHeaders(mutableURLRequest, oauthMethod: oauthMethod, url: absUrl, parameters: parameters)
            return mutableURLRequest
        }
    }

    private static func baseReturn (mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, absUrl: NSURL, parameters: [String : AnyObject]) -> NSMutableURLRequest {
        let mur = Router.setHeaders(mutableURLRequest, oauthMethod: oauthMethod, url: absUrl, parameters: parameters)
        return Alamofire.ParameterEncoding.URLEncodedInURL.encode(mur, parameters: parameters).0
    }

    private static func setHeaders(mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> NSMutableURLRequest {
        let headers = Router.buildHeaders(oauthMethod, url:url, parameters: parameters)
        for (field, value) in headers {
            mutableURLRequest.setValue(value, forHTTPHeaderField: field)
        }

//        print("headers----\(headers)")

        return mutableURLRequest
    }

    private static func buildHeaders(requestMethod: OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> [String : String] {
        let oauth_token = "" //replace it,for some personal reason,i don't want to expose it
        let oauth_token_secret = "" //replace it,for some personal reason,i don't want to expose it

        let c = OAuthSwiftClient(consumerKey: Config.consumerKey, consumerSecret: Config.consumerSecret, accessToken: oauth_token, accessTokenSecret: oauth_token_secret)
        let credential = c.credential

        let authorization = credential.authorizationHeaderForMethod(requestMethod,
            url: url,
            parameters: parameters)

        let headers = ["Authorization": authorization]
        return headers
    }
}

the class to request

import Alamofire
import OAuthSwift
import SwiftyJSON

class RequestApi {

    typealias SuccessBlock = (resJson: JSON) -> Void
    typealias FailureBlock = (errorDescription: String) -> Void

    static let Manager = Session.sharedInstance.ApiManager()

    private static func handle(result: Result<AnyObject>, success: SuccessBlock, failure: FailureBlock) {
        switch result {
        case .Success:
            let resJson = JSON(result.value!)
            success(resJson: resJson)
        case .Failure(_, let error):
            let description = (error as NSError).localizedDescription
            Tip().show(TipType.Failure, text: description)
            failure(errorDescription: description)
        }
    }

    static func retweet(id: Int, success: SuccessBlock, failure: FailureBlock) {
        Manager.request(Router.Retweet(id)).responseJSON  { request, response, result in
            handle(result, success: success, failure: failure)
        }
    }

    static func update(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) {
        Manager.request(Router.UpdateStatus(parameters)).responseJSON  { request, response, result in
            handle(result, success: success, failure: failure)
        }
    }

    static func readHomeTimeline(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) {
        Manager.request(Router.ReadHomeTimeline(parameters)).responseJSON  { request, response, result in
            handle(result, success: success, failure: failure)
        }
    }

    static func readUserTimeline(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) {
        Manager.request(Router.ReadUserTimeline(parameters)).responseJSON  { request, response, result in
            handle(result, success: success, failure: failure)
        }
    }

    static func fetchInstagramOembed(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) {
        Manager.request(InstagramRouter.FetchOembed(parameters)).responseJSON  { request, response, result in
            handle(result, success: success, failure: failure)
        }
    }

}

other class

//
//  Session.swift
//  Twicrow
//
//  Created by fewspider on 15/12/7.
//  Copyright © 2015年 fff. All rights reserved.
//

import Alamofire

class Session {
    static let sharedInstance = Session()

    private var manager : Manager?

    func ApiManager() -> Manager {
        if let m = self.manager {
            return m
        }else{
            let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
            configuration.timeoutIntervalForRequest = 5

            let tempmanager = Alamofire.Manager(configuration: configuration)
            self.manager = tempmanager
            return self.manager!
        }
    }
}

Reproduce the issue

var dct = [String : AnyObject]()
dct["in_reply_to_status_id"] = 676067907940384768
dct["status"] = "Test RT @fewspider: 还是仔细研究下 https://t.co/bLcuzJp1ep"
RequestApi.update(dct, success: { (resJson) -> Void in
        print("resJson----\(resJson)")
    }, failure: { (errorDescription) -> Void in
})

Not the encode problem,I change Alamofire ParameterEncoding.swift line 209,add ? and /,beacuse twitter update status is encode in url,not json.

let generalDelimitersToEncode = ":#[]@?/" // does not include "?" or "/" due to RFC 3986 - Section 3.4
Originally created by @ohcrfpv on GitHub (Dec 13, 2015). Original GitHub issue: https://github.com/OAuthSwift/OAuthSwift/issues/156 OAuthSwift version is `0.4.8` Twitter API is `https://api.twitter.com/1.1/statuses/update.json`, you can visit `https://dev.twitter.com/rest/reference/post/statuses/update` for more detail. When my parameters has `in_reply_to_status_id`,it return like below ``` json { "errors" : [ { "message" : "Could not authenticate you.", "code" : 32 } ] } ``` But only send `status` is ok, it is pretty odd. My project use `https://github.com/Alamofire/Alamofire` to request the Twitter REST API. I write a method to fetch header,do not forget replace the oauth_token,oauth_token_secret and Config,for some personal reason,i don't want to expose it: ``` swift private static func buildHeaders(requestMethod: OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> [String : String] { let oauth_token = "" //replace it,for some personal reason,i don't want to expose it let oauth_token_secret = "" //replace it,for some personal reason,i don't want to expose it let c = OAuthSwiftClient(consumerKey: Config.consumerKey, consumerSecret: Config.consumerSecret, accessToken: oauth_token, accessTokenSecret: oauth_token_secret) let credential = c.credential let authorization = credential.authorizationHeaderForMethod(requestMethod, url: url, parameters: parameters) let headers = ["Authorization": authorization] return headers } ``` and set the header ``` swift private static func setHeaders(mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> NSMutableURLRequest { let headers = Router.buildHeaders(oauthMethod, url:url, parameters: parameters) for (field, value) in headers { mutableURLRequest.setValue(value, forHTTPHeaderField: field) } return mutableURLRequest } ``` The class is: ``` swift // // Router.swift // Twicrow // // Created by fewspider on 15/9/28. // Copyright © 2015年 fff. All rights reserved. // import Foundation import Alamofire import SwiftyJSON import OAuthSwift struct Config { static let consumerKey = "" // replace it static let consumerSecret = "" // replace it static let callbackURL = "oauth-swift://oauth-callback/twitter" } enum Router: URLRequestConvertible { static let baseURLString = "https://api.twitter.com/1.1" case ReadHomeTimeline([String: AnyObject]) case ReadUserTimeline([String: AnyObject]) case UpdateStatus([String: AnyObject]) case Retweet(Int) var method: Alamofire.Method { switch self { case .UpdateStatus, .Retweet: return .POST default: return .GET } } var oauthMethod: OAuthSwiftHTTPRequest.Method { switch self { case .UpdateStatus, .Retweet: return .POST default: return .GET } } var path: String { switch self { case ReadHomeTimeline(_): return "/statuses/home_timeline.json" case ReadUserTimeline(_): return "/statuses/user_timeline.json" case UpdateStatus(_): return "/statuses/update.json" case .Retweet(let id): return "/statuses/retweet/\(id).json" } } // MARK: URLRequestConvertible var URLRequest: NSMutableURLRequest { let URL = NSURL(string: Router.baseURLString)! let absUrl = URL.URLByAppendingPathComponent(path) let mutableURLRequest = NSMutableURLRequest(URL: absUrl) mutableURLRequest.HTTPMethod = method.rawValue switch self { case .UpdateStatus(let parameters): return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters) case .ReadHomeTimeline(let parameters): return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters) case .ReadUserTimeline(let parameters): return Router.baseReturn(mutableURLRequest, oauthMethod: oauthMethod, absUrl: absUrl, parameters: parameters) default: let parameters = [String: AnyObject]() Router.setHeaders(mutableURLRequest, oauthMethod: oauthMethod, url: absUrl, parameters: parameters) return mutableURLRequest } } private static func baseReturn (mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, absUrl: NSURL, parameters: [String : AnyObject]) -> NSMutableURLRequest { let mur = Router.setHeaders(mutableURLRequest, oauthMethod: oauthMethod, url: absUrl, parameters: parameters) return Alamofire.ParameterEncoding.URLEncodedInURL.encode(mur, parameters: parameters).0 } private static func setHeaders(mutableURLRequest: NSMutableURLRequest, oauthMethod:OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> NSMutableURLRequest { let headers = Router.buildHeaders(oauthMethod, url:url, parameters: parameters) for (field, value) in headers { mutableURLRequest.setValue(value, forHTTPHeaderField: field) } // print("headers----\(headers)") return mutableURLRequest } private static func buildHeaders(requestMethod: OAuthSwiftHTTPRequest.Method, url: NSURL, parameters: [String: AnyObject]) -> [String : String] { let oauth_token = "" //replace it,for some personal reason,i don't want to expose it let oauth_token_secret = "" //replace it,for some personal reason,i don't want to expose it let c = OAuthSwiftClient(consumerKey: Config.consumerKey, consumerSecret: Config.consumerSecret, accessToken: oauth_token, accessTokenSecret: oauth_token_secret) let credential = c.credential let authorization = credential.authorizationHeaderForMethod(requestMethod, url: url, parameters: parameters) let headers = ["Authorization": authorization] return headers } } ``` the class to request ``` swift import Alamofire import OAuthSwift import SwiftyJSON class RequestApi { typealias SuccessBlock = (resJson: JSON) -> Void typealias FailureBlock = (errorDescription: String) -> Void static let Manager = Session.sharedInstance.ApiManager() private static func handle(result: Result<AnyObject>, success: SuccessBlock, failure: FailureBlock) { switch result { case .Success: let resJson = JSON(result.value!) success(resJson: resJson) case .Failure(_, let error): let description = (error as NSError).localizedDescription Tip().show(TipType.Failure, text: description) failure(errorDescription: description) } } static func retweet(id: Int, success: SuccessBlock, failure: FailureBlock) { Manager.request(Router.Retweet(id)).responseJSON { request, response, result in handle(result, success: success, failure: failure) } } static func update(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) { Manager.request(Router.UpdateStatus(parameters)).responseJSON { request, response, result in handle(result, success: success, failure: failure) } } static func readHomeTimeline(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) { Manager.request(Router.ReadHomeTimeline(parameters)).responseJSON { request, response, result in handle(result, success: success, failure: failure) } } static func readUserTimeline(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) { Manager.request(Router.ReadUserTimeline(parameters)).responseJSON { request, response, result in handle(result, success: success, failure: failure) } } static func fetchInstagramOembed(parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock) { Manager.request(InstagramRouter.FetchOembed(parameters)).responseJSON { request, response, result in handle(result, success: success, failure: failure) } } } ``` other class ``` swift // // Session.swift // Twicrow // // Created by fewspider on 15/12/7. // Copyright © 2015年 fff. All rights reserved. // import Alamofire class Session { static let sharedInstance = Session() private var manager : Manager? func ApiManager() -> Manager { if let m = self.manager { return m }else{ let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() configuration.timeoutIntervalForRequest = 5 let tempmanager = Alamofire.Manager(configuration: configuration) self.manager = tempmanager return self.manager! } } } ``` Reproduce the issue ``` swift var dct = [String : AnyObject]() dct["in_reply_to_status_id"] = 676067907940384768 dct["status"] = "Test RT @fewspider: 还是仔细研究下 https://t.co/bLcuzJp1ep" RequestApi.update(dct, success: { (resJson) -> Void in print("resJson----\(resJson)") }, failure: { (errorDescription) -> Void in }) ``` Not the encode problem,I change Alamofire `ParameterEncoding.swift` line 209,add `?` and `/`,beacuse twitter update status is encode in url,not json. ``` swift let generalDelimitersToEncode = ":#[]@?/" // does not include "?" or "/" due to RFC 3986 - Section 3.4 ```
kerem closed this issue 2026-03-03 16:45:38 +03:00
Author
Owner

@ohcrfpv commented on GitHub (Dec 13, 2015):

@phimage It seems the Blank string in parameters is not encoded.

<!-- gh-comment-id:164280507 --> @ohcrfpv commented on GitHub (Dec 13, 2015): @phimage It seems the Blank string in parameters is not encoded.
Author
Owner

@ohcrfpv commented on GitHub (Dec 13, 2015):

It is real the problem that the blank string in parameters is not encoded. I already solved it :)

<!-- gh-comment-id:164285102 --> @ohcrfpv commented on GitHub (Dec 13, 2015): It is real the problem that the blank string in parameters is not encoded. I already solved it :)
Author
Owner

@phimage commented on GitHub (Dec 13, 2015):

ok good to know
(I started a project in my private server OAuthSwift-Alamofire but not ready for the moment)

Just for info no need to close the issue here, if I merge the PR you make this will close the issue automatically

<!-- gh-comment-id:164289286 --> @phimage commented on GitHub (Dec 13, 2015): ok good to know (I started a project in my private server OAuthSwift-Alamofire but not ready for the moment) Just for info no need to close the issue here, if I merge the PR you make this will close the issue automatically
Author
Owner

@phimage commented on GitHub (Dec 23, 2015):

I try to find a better solution without success by using NSCharacterSet.URLQueryAllowedCharacterSet() or this union of NSCharacterSet URLXXXAllowedCharacterSet

the testSignature failed
urlEncodedStringWithEncoding is used to encode url and parameters, but also to compute signature Maybe there is two things different here to separate!!
Signature is described here https://tools.ietf.org/html/rfc5849#section-3.6 and there is some info about the difference, and the space encoding

This method is different from the encoding scheme used by the
   "application/x-www-form-urlencoded" content-type (for example, it
   encodes space characters as "%20" and not using the "+" character).

With or without space the testSignature success, but maybe because no space is into the test

Anyway I think I will merge your PR
and study #115 where there is some alternative code which use a NSCharacterSet URLXXXAllowedCharacterSet

<!-- gh-comment-id:166987882 --> @phimage commented on GitHub (Dec 23, 2015): I try to find a better solution without success by using `NSCharacterSet.URLQueryAllowedCharacterSet()` or this union of `NSCharacterSet` `URLXXXAllowedCharacterSet` the `testSignature` failed `urlEncodedStringWithEncoding` is used to encode url and parameters, but also to compute signature Maybe there is two things different here to separate!! Signature is described here https://tools.ietf.org/html/rfc5849#section-3.6 and there is some info about the difference, and the space encoding ``` This method is different from the encoding scheme used by the "application/x-www-form-urlencoded" content-type (for example, it encodes space characters as "%20" and not using the "+" character). ``` With or without space the `testSignature` success, but maybe because no space is into the test Anyway I think I will merge your PR and study #115 where there is some alternative code which use a `NSCharacterSet` `URLXXXAllowedCharacterSet`
Author
Owner

@ohcrfpv commented on GitHub (Dec 24, 2015):

@phimage 👍

<!-- gh-comment-id:167036248 --> @ohcrfpv commented on GitHub (Dec 24, 2015): @phimage 👍
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/OAuthSwift#95
No description provided.