Permissions error with Google Analytics API and VB.net - vb.net

Here's what I'm trying to do: I'm trying to generate an access token on the server side that will serve to authenticate a user as in this example:
https://ga-dev-tools.appspot.com/embed-api/server-side-authorization/
I've written this code in VB.net after several tries:
Public GA_Token As String
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim filename As String = {{path to p12 file}}
Dim serviceAccountEmail As String = {{service account generated email from IAM & Admin from console.developers.google.com of the form user#project name.iam.gserviceaccount.com}}
Dim certificate = New X509Certificate2(filename, "password", X509KeyStorageFlags.Exportable Or X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet)
Dim Scopes As IEnumerable(Of String) = {AnalyticsService.Scope.AnalyticsReadonly}
Dim credential As New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With {.Scopes = Scopes}.FromCertificate(certificate))
GA_Token = credential.GetAccessTokenForRequestAsync(Request.Url.ToString, CancellationToken.None).Result
End Sub
GA_Token is then written to the Analytics page I created.
The problem is that, when I go to view the reports, I get a 403 error from Chrome.
Objectbody: "{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"User does not have any Google Analytics account."}],"code":403,"message":"User does not have any Google Analytics account."}}"headers: Objectcache-control: "private, max-age=0"content-encoding: "gzip"content-length: "146"content-type: "application/json; charset=UTF-8"date: "Fri, 18 Nov 2016 18:25:08 GMT"expires: "Fri, 18 Nov 2016 18:25:08 GMT"server: "GSE"vary: "Origin, X-Origin"www-authenticate: "Bearer realm="https://accounts.google.com/", error=insufficient_scope, scope="https://www.googleapis.com/auth/analytics.edit""proto: Object__defineGetter__: defineGetter()defineSetter: defineSetter()lookupGetter: lookupGetter()lookupSetter: lookupSetter()constructor: Object()hasOwnProperty: hasOwnProperty()isPrototypeOf: isPrototypeOf()propertyIsEnumerable: propertyIsEnumerable()toLocaleString: toLocaleString()toString: toString()valueOf: valueOf()get proto: proto()set proto: proto()result: Objecterror: Objectcode: 403errors: Array[1]0: Objectlength: 1__proto__: Array[0]message: "User does not have any Google Analytics account."proto: Object__proto__: Objectstatus: 403statusText: null__proto__: Object_.nH # cb=gapi.loaded_0:606_.du.Vh # cb=gapi.loaded_0:742(anonymous function) # view-selector2.js:109h.o0 # cb=gapi.loaded_0:75xs # cb=gapi.loaded_0:78Wq # cb=gapi.loaded_0:78_.C.uea # cb=gapi.loaded_0:77Ap # cb=gapi.loaded_0:71
cb=gapi.loaded_0:67 Uncaught Object {result: Object, body: "{"error":{"errors":[{"domain":"global","reason":"i…er does not have any Google Analytics account."}}", headers: Object, status: 403, statusText: null}(anonymous function) # cb=gapi.loaded_0:67
Now, I can use the OAuth2 protocol to generate the token and have it display if the user logs in to his/her account, but I'm trying to bypass that. The problem I'm running into is that I don't see where I'm supposed to set the permissions for the account. I went into IAM and Admin and enabled Domain-wide Delegation on two different service accounts to test with. I set them with every permission imaginable (which worked out to Owner + 18 other permissions). Where else do I need to set permissions, or am I overlooking something?

First, set the User email like this here, not sure how to do that in VB.
You say:
I can use the OAuth2 protocol to generate the token and have it display if the user logs in to his/her account, but I'm trying to bypass that.
But I see that you're also trying to use a Google Service Account, which I think only works if you are a paying G Suite user.
I've spent a lot of time to figure this out for my Gmail client and by accident found the solution:
The permissions should be visible per user under Connected Apps.
But this does mean however that your user has to manually grant access once, after that you can just get a token using the Google Service Account.

I tried and it works for me.
I had previously setup a Project called "YYYassistant" and I created an account:
Then I created a simple WinForm app and could not reproduce the issue, this works:
Imports Google.Apis.Auth.OAuth2
Imports System.Security.Cryptography.X509Certificates
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim scopes As String() = New String() {AnalyticsService.Scope.Analytics}
' view and manage your Google Analytics data
Dim keyFilePath = "C:\Temp\YYYassistant-1234db98765.p12"
' Downloaded from https://console.developers.google.com
Dim serviceAccountEmail = "XXX#YYYassistant.iam.gserviceaccount.com"
' found https://console.developers.google.com
'loading the Key file
Dim certificate = New X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable)
Dim credential = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With {.Scopes = scopes}.FromCertificate(certificate))
End Sub
End Class
"code":403,"message":"User does not have any Google Analytics account."
Its a problem with your account, make a new one.

The issue is explained on the Json
"{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"User does not have any Google Analytics account."}]
{ "User does not have any Google Analytics account." }
Maybe change the code will not change anything, because it's an access error.
In a few words, You gmail used to get the Token has no account linked.
The validation process for the Auth look wells. Before change code i recommend you do the following steps.
1.- Get a valid Email and Passwords
I can believe you already got this (you already have the toke), grab this credentials and go to
https://analytics.google.com/analytics/web/ , login with these credential and And see if you have any account linked.
If you account have no access you will see
Note: If you use the Oauth validate, the email used to get the token can be different that the one used on the project, maybe you need to use someone else's credentials. Be 100% sure of what credential are you using.
2.- If you have no account you need to ask access, so the user with the Google Analytics Account needs to give you access. In the desired account you need to go Admin -> User Management , and add the email used on the Validation Process:
When you have you token, remember store it and refresh it whenever you need, the tokens needs to be mananged
3.- If you want to get your own data, maybe you can use a P12key, as Jeremy says, this method is more easy to validate, but you need to add the p12key on every query (usually the library does it for you). But with this method you can only access to data linked to your own gmail account. If you change the method you can have the same error, that it the need to be 100% of the used account

Related

Azure Sql Server MFA connection from .NET application

My goal is to allow users to connect to our Azure Sql Server using their Azure Active Directory credentials. I'm trying to follow the steps in this article, but I'm getting an error I can't sort out:
Connect to Azure SQL Database with Azure Multi-Factor Authentication
Below are the appropriate pieces of my code, which I largely copied from the example in the article (except my app is written in VB.NET so I had to translate). It requires the Microsoft.IdentityModel.Clients.ActiveDirectory assembly, which I got from NuGet.
Public Module DB
Private ConnectionProvider As ActiveDirectoryAuthProvider
'Gets run at application start
Public Sub SetProvider()
ConnectionProvider = New ActiveDirectoryAuthProvider
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, ConnectionProvider)
End Sub
End Module
'I can't believe Microsoft doesn't just have this as a class that's already been written
Public Class ActiveDirectoryAuthProvider
Inherits SqlAuthenticationProvider
Private ReadOnly _clientId As String = "Our Client ID From The Azure Portal"
Private ReadOnly _redirectUri As New Uri("A Valid URL")
Public Overrides Async Function AcquireTokenAsync(parameters As SqlAuthenticationParameters) As Task(Of SqlAuthenticationToken)
Dim authContext As New AuthenticationContext(parameters.Authority)
authContext.CorrelationId = parameters.ConnectionId
Dim result As AuthenticationResult
Select Case parameters.AuthenticationMethod
Case SqlAuthenticationMethod.ActiveDirectoryInteractive
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, _redirectUri, New PlatformParameters(PromptBehavior.Auto), New UserIdentifier(parameters.UserId, UserIdentifierType.RequiredDisplayableId))
Case SqlAuthenticationMethod.ActiveDirectoryIntegrated
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserCredential())
Case SqlAuthenticationMethod.ActiveDirectoryPassword
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserPasswordCredential(parameters.UserId, parameters.Password))
Case Else
Throw New InvalidOperationException()
End Select
Return New SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)
End Function
Public Overrides Function IsSupported(ByVal authenticationMethod As SqlAuthenticationMethod) As Boolean
Return authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryIntegrated OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryInteractive OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryPassword
End Function
End Class
'And finally, I create new connections like this:
New SqlConnection($"Server=tcp:ourserver.database.windows.net,1433;Initial Catalog=OurDatabase;TrustServerCertificate=True;Pooling=False;Encrypt=True;Authentication=""Active Directory Interactive"";User ID={Environment.UserName}#OurDomain.com;")
Using this code, I do get the popup from Azure asking me to sign in when I run SqlConnection.Open. As soon as I've signed in however, I get the following exception:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException
AADSTS7000218: The request body must contain the following parameter:
'client_assertion' or 'client_secret'.
Any idea how I can fix that?
So, in digging through every resource I could find, I came across this question:
“error_description”:"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'
The answer linked above points to the "Redirect URI" registered with the application in Azure as the cause of the issue.
The Microsoft article in my question states: "For this article, any valid value is fine for RedirectUri, because it isn't used here." The example they use is: "https://mywebserver.com/".
Contrary to the quote from Microsoft, the answer I linked above points out that Azure uses the Redirect URI to determine the type of application that is being registered. Changing the URI from my company's website to "https://login.microsoftonline.com/common/oauth2/nativeclient" fixes my problem. That URL is one of the default values Azure lets you pick from. It evidently indicates to Azure that you are registering a native app, not a web app. Once Azure knows this, it seems to stop demanding a "'client_assertion' or 'client_secret'", which I can only assume are things required for web app authentication.
I faced with this problem a bit earlier, spent a lot of time to figure out the problem and the only solution which helped was adding of "https://login.microsoftonline.com/common/oauth2/nativeclient" on "Authentication" tab to "Redirect URIs" like it was mentioned by Keith Stein

How do I administratively set a new password for ASP.net Identity User who forgot their password?

I am not looking for a solution that involves the user, a token generated, and emailing in order to reset a user's password.
The scenario is a user contacts the admins and asks them to reset their password (internal organization web app). They are then told what that new temporary password is so they can log in and change it.
I see no function that lets me do the above. My attempt:
string passwordToken = await UM.GeneratePasswordResetTokenAsync(user.Id);
IdentityResult res = await UM.ResetPasswordAsync(user.Id, passwordToken, "newPassword##!$%");
UM is UserManager.
I get error "No IUserTokenProvider is registered". I think GeneratePasswordResetToken is the one causing the error. If so, why?
How do I properly do what I need?
Use the combination of RemovePasswordAsync and AddPasswordAsync
UserManager.RemovePasswordAsync(user.Id);
UserManager.AddPasswordAsync(user.Id, tempPassword);

(Google App Script) Can i give access to other users to my private Spreadsheet with oAuth?

i need help for my application "Google App Script".
I am the owner of a Spreadsheet that I use as a DB in my application; this spreadsheet must remain private.
My application is executed as Gadget in Google Site, in this application a user runs the script as himself (not under the owner's identity).
I need that all users who access the application can get some data from the DB Spreadsheet.
How can users get this data, if the Spreadsheet is only accessible to me?
Can I use oAuth?
Sorry for the bad English
Following Zig answer and to illustrate, here is an example of such a contentService webapp, one can call it with this url either in a browser or in urlFetch
The app is deployed as follows : execute as me and anyone can access even anonymous
https://script.google.com/macros/s/AKfycbxfk5YR-JIlhv7HG9R7F-cPxmL0NZRzrdGF4VFGxGivBkYeZY4/exec?&user=chris&row=4&sheet=Sheet1
and here is the demo script
function doGet(e) {
if(e.parameter.user!='serge' && e.parameter.user!='chris' ){return ContentService.createTextOutput("logging error, you are not allowed to see this").setMimeType(ContentService.MimeType.TEXT)};
var sheet = e.parameter.sheet;
var row = Number(e.parameter.row);
Logger.log(sheet+' '+row);
var ss = SpreadsheetApp.openById("0AnqSFd3iikE3dENnemR2LVFMTFM5bDczNGhfSG11LVE");// this sheet is private but anyone can call this app
var sh = ss.getSheetByName(sheet);
var range = sh.getRange(row,1,1,sh.getLastColumn());
var val = Utilities.jsonStringify(range.getValues());
var result = ContentService.createTextOutput(val).setMimeType(ContentService.MimeType.JSON);
return result;
}
No you cant use oauth from the gadget as the user doesnt have read permission.
However you can publish a second script to extract needed data that runs as you with anonymous public access and call that one with urlfetch from the 1st. Slower thou.

DropboxUnlinkedException but the session already had token inside and user didn't revoke the access

My problem is I have existing user in database which store the key and secret from the first authentication. I wish to reuse it again when I come back. For the first time authentication, everything working fine. I can use every method call from Dropbox API and the Token(key and secret) was stored in database.
I come back to app and get the Token from database, set it to the session, link current session with API.
session = new WebAuthSession(appKeys, ACCESS_TYPE);
api = new DropboxAPI<WebAuthSession>(session);
String userKey = dropboxUserObj.getUserKey(); //Key from database
String userSecret = dropboxUserObj.getUserSecret();//Secret from database
AccessTokenPair userAccessTokenPair = new AccessTokenPair(userKey, userSecret);
session.setAccessTokenPair(userAccessTokenPair);
It return DropboxUnlinkedException to me when I want to get user data from api using
String userDisplayName = api.accountInfo().displayname;
I have checked on debug mode. Api was linked with the current session. The current session stored Appkey and user's token and correct access type. The point that I doubt is I saw "client = null". I maybe forgot something but I check them all, try every possibilities I can think of but it still return me "DropboxUnlinkedException" which mean I haven't set an access token pair on the session and I didn't revoke access for sure.
Please help me figure out...
I added a screenshot maybe it can illustrate my problem

Error changing password of user using WinNT string connection (and authenticating the current password)

I am developing a website which will authenticate the user and change the old password with new password.
I am using WinNT string connection and setting password, without the old password check, works.
My code is as below:
'actual setting password
Dim entryD As New DirectoryEntry("WinNT://" + Environment.MachineName + ",computer")
Dim NewUser As DirectoryEntry = entryD.Children.Find(username, "user")
Dim nativeobject As Object = NewUser.NativeObject
NewUser.Invoke("SetPassword", New Object() {strPassNew})
NewUser.CommitChanges()
'setting password ends
This works fine, but authentication code is not working.
It is as below:
'authentication starts
Dim adsiPath As String
adsiPath = String.Format("WinNT://{0}/{1},user", domain, username)
Dim userEntry = New DirectoryEntry(adsiPath, username, password, AuthenticationTypes.Secure)
'Dim nativeobject1 As Object = userEntry.NativeObject
Dim newobj As ActiveDs.IADsUser = userEntry.NativeObject
authent = True
'authentication ends
This authenticates but the exception which it throws is:
logon failure: unknown username or bad password
for the first time, but if i do it again the error is:
"Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again.
"
Which I don't want to happen... I don't want to use LDAP, I want a solution please, to authenticate the old password. Please help?
I had this error in my code where requirement was to add domain users to local admin group of a system. While testing the code after the first try I used to get "Multiple connections to a server ..." error and then I if try the code code later(an hour or so) it worked fine. After searching thru various forms I came know the we can see user logged-on to a computer using
NET SESSION /LIST
command in cmd of remote system and it appears that when we use WinNT provider it actually creates an user session with idle timeout on remote computer (there can be a situation where other programs are creating sessions) which conflicts the connection when you try for second time. So I tried deleting the previous session by
NET SESSION \\RequestingComputerName /DELETE
then I did n't got the error. If that doesn't solve the problem then the last resort is to restart the system.