How to create folder and files in google drive using swift - ios

I am using google drive SDK for folder creation, but unable to create. I am able to login and get all files and folder but unable to create it.
I am using swift and used this code
let metaData = GTLRDrive_File()
metaData.name = "xyz"
metaData.mimeType = "application/vnd.google-apps.folder"
let querys = GTLRDriveQuery_FilesCreate.query(withObject: metaData, uploadParameters: nil)
querys.fields = "id"
//service.executeQuery(querys, delegate: self, didFinish: nil)
self.service.executeQuery(querys) { (ticket:GTLRServiceTicket, object:Any?, error:Error?) in
// Put your completion code here
}
But unable to create folder. Can anyone help me out. Thanks in advance.

func chilkatTest() {
var success: Bool = true
// It requires the Chilkat API to have been previously unlocked.
// See Global Unlock Sample for sample code.
// This example uses a previously obtained access token having permission for the
// Google Drive scope.
let gAuth = CkoAuthGoogle()
gAuth.AccessToken = "GOOGLE-DRIVE-ACCESS-TOKEN"
let rest = CkoRest()
// Connect using TLS.
var bAutoReconnect: Bool = true
success = rest.Connect("www.googleapis.com", port: 443, tls: true, autoReconnect: bAutoReconnect)
// Provide the authentication credentials (i.e. the access token)
rest.SetAuthGoogle(gAuth)
// -------------------------------------------------------------------------
// A multipart upload to Google Drive needs a multipart/related Content-Type
rest.AddHeader("Content-Type", value: "multipart/related")
// Specify each part of the request.
// The 1st part is JSON with information about the file.
rest.PartSelector = "1"
rest.AddHeader("Content-Type", value: "application/json; charset=UTF-8")
let json = CkoJsonObject()
json.AppendString("name", value: "testHello.txt")
json.AppendString("description", value: "A simple file that says Hello World.")
json.AppendString("mimeType", value: "text/plain")
// To place the file in a folder, we must add a parents[] array to the JSON
// and list the folder id's. It's possible for a file to be in multiple folders at once
// if it has more than one parent. If no parents are specified, then the file is created
// in the My Drive folder.
// Note: We'll assume we already have the id if the folder. It is the id's that are specified here,
// not the folder names.
var parents: CkoJsonArray? = json.AppendArray("parents")
var folderId: String? = "0B53Q6OSTWYolY2tPU1BnYW02T2c"
parents!.AddStringAt(-1, value: folderId)
parents = nil
rest.SetMultipartBodyString(json.Emit())
// The 2nd part is the file content, which will contain "Hello World!"
rest.PartSelector = "2"
rest.AddHeader("Content-Type", value: "text/plain")
var fileContents: String? = "Hello World!"
rest.SetMultipartBodyString(fileContents)
var jsonResponse: String? = rest.FullRequestMultipart("POST", uriPath: "/upload/drive/v3/files?uploadType=multipart")
if rest.LastMethodSuccess != true {
print("\(rest.LastErrorText)")
return
}
// A successful response will have a status code equal to 200.
if rest.ResponseStatusCode.integerValue != 200 {
print("response status code = \(rest.ResponseStatusCode.integerValue)")
print("response status text = \(rest.ResponseStatusText)")
print("response header: \(rest.ResponseHeader)")
print("response JSON: \(jsonResponse!)")
return
}
// Show the JSON response.
json.Load(jsonResponse)
// Show the full JSON response.
json.EmitCompact = false
print("\(json.Emit())")
// A successful response looks like this:
// {
// "kind": "drive#file",
// "id": "0B53Q6OSTWYoldmJ0Z3ZqT2x5MFk",
// "name": "Untitled",
// "mimeType": "text/plain"
// }
// Get the fileId:
print("fileId: \(json.StringOf("id"))")
}
Link for libraries needed:-
Download libraries
Include CkoAuthGoogle, CkoRest and CkoJsonObject header files in your project.

It is basically due to the scope, I have to give kGTLRAuthScopeDriveFile in scope area
private let scopes = [kGTLRAuthScopeDriveReadonly,kGTLRAuthScopeDriveFile]
and rest same in google
func folder(){
let metadata = GTLRDrive_File()
metadata.name = "eBilling"
metadata.mimeType = "application/vnd.google-apps.folder"
let querys = GTLRDriveQuery_FilesCreate.query(withObject: metadata, uploadParameters: nil)
querys.fields = "id"
self.service.executeQuery(querys, completionHandler: {(ticket:GTLRServiceTicket, object:Any?, error:Error?) in
if error == nil {
self.listFiles()
}
else {
print("An error occurred: \(error)")
}
})
}

Swift 5
If you are looking to just create a folder without uploading a file with it,
I was able to create a drive folder using Google's REST endpoint like this.
This function takes the auth token and a filename and parameters to create a URLRequest that can then be sent off within a URLSession.
func createFolderRequest(authToken: String, folderName: String) -> URLRequest {
let headers = [
"Content-Type": "multipart/related; boundary=123456789",
"Authorization": "Bearer " + authToken
]
let body =
"""
--123456789
Content-Type: application/json
{
"name": "\(folderName)",
"mimeType": "application/vnd.google-apps.folder"
}
--123456789--
"""
var request = URLRequest(url: URL(string: "https://www.googleapis.com/upload/drive/v3/files")!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = body.data(using: .utf8)
request.addValue(String(body.lengthOfBytes(using: .utf8)), forHTTPHeaderField: "Content-Length")
return request
}
I referenced the google docs for multipart file uploads here

Related

swift:receive AWS id_token after sending facebook current token to AWS Cognito?

I want to send the facebook access token to the AWS Cognito and then receive an authorization token which can further be sent as an Authorization header in HTTP Put request.
However, I always get "unauthorized" response from the AWS end point.
When I try to print :
credentialsProvider.credentials().continueOnSuccessWith(executor: AWSExecutor.default()) { (task) -> Any? in
print(task.error)
return true
}
I get the following output:
Optional(Error Domain=com.amazonaws.AWSJSONBuilderErrorDomain Code=4 "serialized object is neither a valid json Object nor NSData object: {
IdentityPoolId = "******";
Logins = {
"graph.facebook.com" = "<FBSDKAccessToken: *******>";
};
}" UserInfo={NSLocalizedDescription=serialized object is neither a valid json Object nor NSData object: {
IdentityPoolId = "*****+*";
Logins = {
"graph.facebook.com" = "<FBSDKAccessToken: ******>";
};
}})
This is my code:
import AWSCognito
class FacebookProvider: NSObject, AWSIdentityProviderManager {
func logins() -> AWSTask<NSDictionary> {
if let token = FBSDKAccessToken.current() {
return AWSTask(result: [AWSIdentityProviderFacebook:token])
}
return AWSTask(error:NSError(domain: "Facebook Login", code: -1 , userInfo: ["Facebook" : "No current Facebook access token"]))
}
}
class API {
..............
public func putOrder(when fbLogin: Bool, _ order: Order, onSuccess: #escaping(JSON) -> Void,
on Failure: #escaping(Error)-> Void) {
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .EUCentral1 ,
identityPoolId:"*****", identityProviderManager:FacebookProvider())
let configuration = AWSServiceConfiguration(region: AWSRegionType.EUCentral1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let url = "\(serverURL)\(API.loginOrderPath)"
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
urlRequest.httpMethod = API.apiMethodPut
urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("\(credentialsProvider.credentials())", forHTTPHeaderField: "Authorization")
do {
var json: JSON
json = ["companyId": order.companyId, "drinks": order.drinksId, "payment": order.payment, "tip": order.tip]
urlRequest.httpBody = try json.rawData()
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest, completionHandler: {data, response, error -> Void in
if error != nil {
Failure(error!)
} else {
if let response = try? JSON(data: data!) {
onSuccess(response)
} else {
}
}
})
task.resume()
} catch _ {
}
}
}
Expected Result: JSON response from the AWS Server
Actual Result : unauthorised
The reason why your are receiving an unauthorized response from API Gateway is double :
credentialsProvider.credentials() is not serialized to JSON and can not be "as is" for the authorization headers.
Looks like you are trying to manually call API Gateway, by managing your self the low level details of the URL Request. I don't see code to add a signature to the request. All authenticated API Gateway requests must be signed (see https://docs.aws.amazon.com/apigateway/api-reference/making-http-requests/) and the Authorization header must contain the credentials used to compute the signature.
Managing the low level details of AWS Signature is not trivial. You should not write code to do that but use the AWS iOS SDK instead. In particular, if you're trying to call API Gateway with Cognito User Pool authorisation, have a look at this example : https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk-ios-swift.html.
The API Gateway console will generate the client side code required to run this example (see https://docs.aws.amazon.com/apigateway/latest/developerguide/genearte-ios-sdk-of-an-api.html)
This should remove a lot of boiler plate from your code, making it easier to read and to maintain.
The flow posted in your comment above, the iOS SDK will take care of 2/ 3/ and 4/ steps for you - automatically.

Siesta REST login

How to translate my login user URLSession code into Siesta framework code? My current attempt isn't working.
I've looked at the example in the GithubBrowser but the API I have doesn't work like that.
The issue is that the user structure is kind of split by how the endpoint in the API I'm consuming works. The endpoint is http://server.com/api/key. Yes, it really is called key and not user or login. Its called that by the authors because you post a user/pass pair and get a key back. So it takes in (via post) a json struct like:
{"name": "bob", "password": "s3krit"}
and returns as a response:
{"token":"AEWasBDasd...AAsdga"}
I have a SessionUser struct:
struct SessionUser: Codable
{
let name: String
let password: String
let token: String
}
...which encapsulates the state (the "S" in REST) for the user. The trouble is name & password get posted and token is the response.
When this state changes I do my:
service.invalidateConfiguration() // So requests get config change
service.wipeResources() // Scrub all unauthenticated data
An instance is stored in a singleton, which is picked up by the configure block so that the token from the API is put in the header for all other API requests:
configure("**") {
// This block ^ is run AGAIN when the configuration is invalidated
// even if loadManifest is not called again.
if let haveToken = SessionManager.shared.currentUser?.token
{
$0.headers["Authorization"] = haveToken
}
}
That token injection part is already working well, by the way. Yay, Siesta!
URLSession version
This is bloated compared to Siesta, and I'm now not using this but here is what it used to be:
func login(user: SessionUser, endpoint: URL)
{
DDLogInfo("Logging in: \(user.name) with \(user.password)")
let json: [String: Any] = ["name": user.name, "password": user.password]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.httpBody = jsonData
_currentStatus = .Unknown
weak var welf = self
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
handleLogin(error: error, message: "No data from login attempt")
return
}
let jsonData:Any
do {
jsonData = try JSONSerialization.jsonObject(with: data, options: [])
}
catch let jsonDecodeError {
handleLogin(error: jsonDecodeError, message: "Could not get JSON from login response data")
return
}
guard let jsonDecoded = jsonData as? [String: Any] else {
handleLogin(error: error, message: "Could not decode JSON as dictionary")
return
}
guard let token = jsonDecoded["token"] as? String else {
handleLogin(error: error, message: "No auth token in login response")
return
}
let newUser = SessionUser(name: user.name, password: "", token: token)
welf?.currentUser = newUser
welf?.saveCurrentSession()
welf?._currentStatus = .LoggedIn
DDLogInfo("User \(newUser.name) logged in")
loginUpdate(user: newUser, status: .LoggedIn, message: nil, error: nil)
}
task.resume()
}
Siesta Version
Here is my attempt right now:
func login(user: String, pass: String, status: #escaping (String?) -> ())
{
let json = [ "name": user, "password": pass]
let req = ManifestCloud.shared.keys.request(.post, json: json)
req.onSuccess { (tokenInfo) in
if let token = tokenInfo.jsonDict["token"] as? String
{
let newUser = SessionUser(name: user, password: pass, token: token)
self.currentUser = newUser
}
status("success")
}
req.onFailure { (error) in
status(error.userMessage)
}
req.onCompletion { (response) in
status(nil)
}
}
Its sort of working, but the log in credentials are not saved by Siesta and I've had to rig up a new notification system for login state which I'd hoped Siesta would do for me.
I want to use Siesta's caching so that the SessionUser object is cached locally and I can use it to get a new token, if required, using the cached credentials. At the moment I have a jury-rigged system using UserDefaults.
Any help appreciated!
The basic problem here is that you are requesting but not loading the resource. Siesta draws a distinction between those two things: the first is essentially a fancied-up URLSession request; the second means that Siesta hangs on to some state and notifies observers about it.
Funny thing, I just answered a different but related question about this a few minutes ago! You might find that answer a helpful starting point.
In your case, the problem is here:
let req = ManifestCloud.shared.keys.request(.post, json: json)
That .request(…) means that only your request hooks (onSuccess etc.) receive a notification when your POST request finishes, and Siesta doesn’t keep the state around for others to observe.
You would normally accomplish that by using .load(); however, that creates a GET request and you need a POST. You probably want to promote your POST to be a full-fledge load request like this:
let keysResource = ManifestCloud.shared.keys
let req = keysResource.load(using:
keysResource.request(.post, json: json))
This will take whatever that POST request returns and make it the (observable) latestData of ManifestCloud.shared.keys, which should give you the “notification system for login state” that you’re looking for.

Getting token from an asp.net web api in iOS / swift

I am a .net developer but very new to iOS and swift development, just need help with consuming Web API using Swift2
The Asp.net Web API has been built with OAuth2 authentication, published to my Azure VM server with SSL certificate installed. The API site itself works properly, tested through Postman
However I got stuck when started writing first few lines of code in Swift trying to get Authentication token. After reading some online tutorials I decided to engage Alamofire, and produced below codes snippet:
func GetToken() {
let params = [
"grant_type" : "password",
"username" : "123456#qq.com",
"password" : "averygoodpassword"
]
let headers = [
"Content-Type" : "application/x-www-form-urlencoded"
]
request(.POST, "https://api.example.com/token",
parameters: params,
headers: headers,
encoding: .JSON)
.responseJSON { request, response, result in
print (request)
print (response?.description)
print (result)
switch result {
case .Success(let JSON):
print("Success with JSON: \(JSON)")
case .Failure(let data, let error):
print("Request failed with error: \(error)")
if let data = data {
print("Response data: \(NSString(data: data, encoding: NSUTF8StringEncoding)!)")
}
}
}
}
It ends up with below output in Xcode which didn't seem to be OK. The error = unsupported_grant_type told me that the request were sent to server but the parameters were not sent with request properly. I really cannot figure out the reason and solution, had been digging on Internet for a few days but still feeling desperate with it. Can anyone help please? Even if someone can provide a pure swift solution without any 3rd party library will be greatly helpful. Thanks!
Xcode output:
Optional( { URL: https://api.example.com/token })
Optional(" { URL: https://api.example.com/token } { status code: 400, headers {\n \"Access-Control-Allow-Headers\" = \"Content-Type\";\n \"Access-Control-Allow-Methods\" = \"GET, POST, PUT, DELETE, OPTIONS\";\n \"Access-Control-Allow-Origin\" = \"*\";\n \"Cache-Control\" = \"no-cache\";\n \"Content-Length\" = 34;\n \"Content-Type\" = \"application/json;charset=UTF-8\";\n Date = \"Fri, 30 Sep 2016 10:30:31 GMT\";\n Expires = \"-1\";\n Pragma = \"no-cache\";\n Server = \"Microsoft-IIS/8.5\";\n \"X-Powered-By\" = \"ASP.NET\";\n} }")
SUCCESS
Success with JSON: {
error = "unsupported_grant_type";
}
I had a similar problem trying to POST to MailGun for some automated emails I was implementing in an app.
I was able to get this working properly with a large HTTP response. I put the full path into Keys.plist so that I can upload my code to github and broke out some of the arguments into variables so I can have them programmatically set later down the road.
// Email the FBO with desired information
// Parse our Keys.plist so we can use our path
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
// variablize our https path with API key, recipient and message text
let mailgunAPIPath = dict["mailgunAPIPath"] as? String
let emailRecipient = "bar#foo.com"
let emailMessage = "Testing%20email%20sender%20variables"
// Create a session and fill it with our request
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: mailgunAPIPath! + "from=FBOGo%20Reservation%20%3Cscheduler#<my domain>.com%3E&to=reservations#<my domain>.com&to=\(emailRecipient)&subject=A%20New%20Reservation%21&text=\(emailMessage)")!)
// POST and report back with any errors and response codes
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
The Mailgun Path is in Keys.plist as a string called mailgunAPIPath with the value:
https://API:key-<my key>#api.mailgun.net/v3/<my domain>.com/messages?
I'm slightly opposed to using 3rd party libraries, especially for small things like a http POST and this seems like a much more maintainable solution to me. Anyways, hope this helps, let me know if you have any questions!

Swift POST request delivers body to node.js/express application in wrong format

I am trying to send a JSON object from an ios app using Swift. Everything works but the fact that the body in the request body on the node.js/express backend is in an aca-awkward format where the whole JSON object parsed in Swift is the actual key. So what is being received on the server is:
req.body = { '{"email":"email","password":"password"}': '' }
I want it to be:
{ "email":"email","password":"password" }
So I can access the key-values with, for example, req.body.email
I am new to http communication between ios and nodejs so maybe this is
normal but its very annoying.
My ios http post code is :
let url = NSURL(string: "http://localhost:3000/users")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "POST"
var login_details: [String: AnyObject] = [
"email" : "\(self.email_field.text)",
"password" : "\(self.password_field.text)"
]
let valid = NSJSONSerialization.isValidJSONObject(login_details)
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(login_details, options: nil, error: &err)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
}
}
My node router module is:
router.post('/', function(req, res) {
console.log(req.body);
});
Node automatically parses the JSON request body to object only if it knows that it is the right content type. You have two possible solutions, use one or the other but not both since they conflict.
1) client side, more correct IMO. Set the right content type. Add this to your Swift code:
request.setValue("application/json" forHTTPHeaderField:"Content-Type")
2) server side. Just parse the body yourself:
parsedBody = JSON.parse(body)
// parsedBody will now be an object

Uploading to azure blob storage from SAS URL returns 404 status

I am using Azure Mobile Services API endpoint to return a private shared access signature URL to my azure storage container like so:
var blobService = azure.createBlobService(accountName, key, host);
blobService.createContainerIfNotExists(containerName, function(err) {
if (err) {
cb(err, null);
return;
}
// Generate a 5 minute access
var expiryDate = minutesFromNow(5);
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: azure.Constants.BlobConstants.SharedAccessPermissions.WRITE,
Expiry: expiryDate
}
};
// Generate the URL with read access token
var sasURL = blobService.generateSharedAccessSignature(containerName, blobName, sharedAccessPolicy);
var urlForDownloading = sasURL.baseUrl + sasURL.path + '?' + qs.stringify(sasURL.queryString);
cb(null, urlForDownloading);
});
function minutesFromNow(minutes) {
var date = new Date();
date.setMinutes(date.getMinutes() + minutes);
return date;
};
I then return this URL to my iOS client to upload the file and process it as:
client.invokeAPI("document/\(document.idValue).\(document.fileExtension)",
body: nil,
HTTPMethod: "PUT",
parameters: nil,
headers: nil) { result, response, error in
if let dictResult = result as? NSDictionary {
// Get the SAS URL to write directly to the blob storage
if let location = dictResult["location"] as? String {
let url = NSURL(string: location)
let request = NSURLRequest(URL: url)
let uploadTask = session.uploadTaskWithRequest(request, fromFile: localFile) { data, response, error in
if completionBlock != nil {
let success = (error == nil && httpResponse.statusCode == 200)
completionBlock!(success)
}
}
}
}
}
uploadTask.resume()
The iOS client gets a 404 response with a message of
<?xml version="1.0" encoding="utf-8"?><Error><Code>ResourceNotFound</Code><Message>The specified resource does not exist.
The container does exist in the storage account and requests to get blobs from the container with the access keys are successful. This new blob won't exist as it is a new upload, but why am I getting a 404 for a write request to the container?
Found the solution...
let request = NSURLRequest(URL: url) produces a GET request and even passing it to uploadTaskWithRequest persists this request type, whereas I thought this call would change it to a PUT or POST request signifying the upload.
Defining the iOS request as
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
was successful. And the response returned was a 201 created.

Resources