ebay API client credentials authorization - go

I want to implement an Ebay Browse API client credentials grant flow in Go using the oauth2 package. Here is my conf struct:
conf := clientcredentials.Config{
ClientID: "My Client ID",
ClientSecret: "My client Secret",
Scopes: []string{"https://api.ebay.com/oauth/api_scope"},
TokenURL: "https://api.sandbox.ebay.com/identity/v1/oauth2/token",
EndpointParams: url.Values{"redirect_uri": {"the RuName"}},
}
Then I tried to make a request by creating an *http.Request:
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client := conf.Client(ctx)
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
But then I get:
{
"errors" : [ {
"errorId" : 1003,
"domain" : "OAuth",
"category" : "REQUEST",
"message" : "Token type in the Authorization header is invalid:Application",
"longMessage" : "Token type in the Authorization header is invalid:Application"
} ]
}
It turned out, that when making the request to the resource server Go's package sets the Authorization header as:
Authorization: Application Access Token token_string
So, I tried to set the token type to Bearer manually by doing:
tok, err := conf.Token(ctx)
if err != nil {
log.Fatal(err)
}
client := conf.Client(ctx)
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer " tok.AccessToken)
res, err := client.Do(req)
But I continue getting the same error.

So, there is a problem (bug?) with the Ebay's implementation of the OAuth2.0 spec and I had to reimplement the TokenSource of the oauth2/clientcredentials package to set the token type to Bearer not Application Access Token.

Related

How to send a message with discord oAuth API

I have a go api, and we are trying to send a message on a channel thanks to oAuth2.
So far, we retrieved the token and we can display some user information (email, username etc...)
we are trying to retrieve some message, but we have an "unauthorized" error.
I read some documentation about permissions, i think i have to do somethings with header, but i'm totally new to golang.
can you help me? :)
this is the code :
func HelloDiscord(w http.ResponseWriter, r *http.Request){
conf := &oauth2.Config{
RedirectURL: "http://localhost:8080/auth/discord",
ClientID: "clientid",
ClientSecret: "clientsecret",
Scopes: []string{discord.ScopeIdentify},
Endpoint: discord.Endpoint,
}
token, _ := conf.Exchange(context.Background(), r.FormValue("code"))
fmt.Println(token)
message, err := conf.Client(context.Background(), token).Get("https://discord.com/api/channels/1021410887191507004/messages")
fmt.Println("message;")
fmt.Println(message)
if err != nil || message.StatusCode != 200 {
w.WriteHeader(http.StatusInternalServerError)
if err != nil {
w.Write([]byte(err.Error()))
} else {
w.Write([]byte(message.Status))
}
return
}
defer message.Body.Close()
body, err := ioutil.ReadAll(message.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(body)
}
thanks a lot

Prevent Cloud Run from passing on Authorization header

I have Grafana hosted on Cloud Run. Grafana has anonymous access enabled while Cloud Run needs valid credentials.
Now I try mimic the first example of https://grafana.com/docs/grafana/latest/http_api/dashboard/. Thus I downloaded a service account key to /tmp/creds.json. This file I set as GOOGLE_APPLICATION_CREDENTIALS.
Using these credentials I then send a POST to /api/dashboards/db.
What I see is that I get through Google auth, it starts up Cloud Run instances and I get an error message from Grafana:
{"message":"invalid API key"}
This in turn then lets me believe that Google auth is not consuming the Authorization header but delegating it along.
So my question is how can I prevent Google auth from delegate this header?
Or as a workaround how can I make Grafana ignore the header?
Here the source I use to make the requests:
func TestCall(t *testing.T) {
r := strings.NewReader(`
{
"dashboard": {
"id": null,
"uid": null,
"title": "Production Overview",
"tags": [ "templated" ],
"timezone": "browser",
"schemaVersion": 16,
"version": 0,
"refresh": "25s"
},
"folderId": 0,
"folderUid": "l3KqBxCMz",
"message": "Made changes to xyz",
"overwrite": false
}
`)
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/creds.json")
ctx := context.TODO()
ts, err := idtoken.NewTokenSource(ctx, "https://grafana-123.run.app")
if err != nil {
if !strings.HasPrefix(err.Error(), "idtoken: credential must be service_account") {
panic(err)
}
creds, err := google.FindDefaultCredentials(ctx)
if err != nil {
panic(err)
}
ts = creds.TokenSource
}
tok, err := ts.Token()
if err != nil {
panic(err)
}
if tok.AccessToken == "" {
panic("Empty token!")
}
fmt.Printf("AT %s\n", tok.AccessToken)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://grafana-123.run.app/api/dashboards/db", r)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
// This does not work at all: req.Header.Set("Proxy-Authorization", "Bearer "+tok.AccessToken)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Printf("SC %d\n", resp.StatusCode)
bs, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
panic(string(bs))
}
}
The header Proxy-Authorization should work, but I have also been unsuccessful.
To clarify your problem, you are using IAP (OIDC Identity Token) to authorize access to Cloud Run. Your software (Grafana) is processing the HTTP Authorization header and assuming that you are passing it an API Key (which fails). I am not familiar with Grafana internals, but one possible solution is to write middleware that detects an OIDC token and removes that header. I am not aware of a Cloud Run method to remove the Authorization header from HTTP requests that your application receives.
IMHO this is a problem that should be resolved in Grafana's code. If Grafana has anonymous access enabled, then it should not be validating the HTTP Authorization header. Consider opening an issue with Grafana or fixing the code and submitting a patch.

How to GET Request with Cookie After login

I tried to get some resp.Body of jadwalURL. jadwalURL can be access after login, so I add the Cookie header to the request. But Sadly the response is not quite that I want (response is home page). I tried this similiar flow with Postman. and I got the jadwalURL body as i wanted. Is there anything wrong with my code? I still dont get the solution after 3 hours searching.
func main() {
data := url.Values{}
data.Set("username", username)
data.Set("password", password)
client := &http.Client{}
r, _ := http.NewRequest(http.MethodPost, loginURL, strings.NewReader(data.Encode()))
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, _ := client.Do(r)
cookie := resp.Cookies()
fmt.Println(cookie)
fmt.Println(resp.Status)
req, err := http.NewRequest(http.MethodGet, jadwalURL, nil)
if err != nil {
panic(err)
}
req.AddCookie(&http.Cookie{
Name: cookie[0].Name,
Value: cookie[0].Value,
Domain: domainURL,
Path: "/",
})
jadwalResp, err := http.DefaultClient.Do(req)
if err != nil {
panic(nil)
}
body, _ := ioutil.ReadAll(jadwalReq.Body)
jadwalResp.Body.Close()
fmt.Println(string(body))
}
Hi If you have this problem I just change create a newClient the problem is i tried to make request with client.
newClient := &http.Client{}
// then
jadwalResp, err := newClient.Do(req)

Cookie not being returned with subsequent REST calls to Go Server

I am exploring OAuth2 authentication and set up a server that authenticates with Github. I followed this example, and was able to get it working. I wanted to continue on with some of the suggestions and implement a basic session token system and make the Github calls from my server as opposed to sending the Authorization token to the client.
Here is my slightly modified /oauth/redirect handler
func oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("/oauth/redirect")
err := r.ParseForm()
if err != nil {
fmt.Fprintf(os.Stdout, "could not parse query: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")
reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
req.Header.Set("accept", "application/json")
res, err := httpClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stdout, "could not send HTTP request: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()
var t oAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
fmt.Fprintf(os.Stdout, "could not parse JSON response: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
newSession := sessionTracker{
AccessToken: accessToken,
TimeOut: time.Now().Add(time.Minute * 15),
}
sessionToken := uuid.New().String()
sessionTrackerCache[sessionToken] = newSession
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Expires: newSession.TimeOut,
Domain: "localhost",
})
http.Redirect(w, r, "/welcome.html", http.StatusFound)
}
It redirects to the welcome page with an attached cookie that includes my SessionToken id.
Here is my welcomeHandler
func welcomeHandler(w http.ResponseWriter, req *http.Request) {
fmt.Println("/welcome")
cookie, err := req.Cookie(sessionTokenConst)
if err != nil {
fmt.Fprintf(os.Stdout, "no cookie attached: %+v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
dat, err := ioutil.ReadFile("./public/welcome.html")
if err != nil {
fmt.Fprintf(os.Stdout, "could not read welcome page: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, string(dat))
}
Observing my browser's network tab, I authenticate with Github and my server is able to see the authorization token. The redirect response at the end of the oauthRedirectHandler contains the cookie with the SessionID. My issue lies in the fact that the browser does not seem to attach the token on the GET call to /welcome.html. I can confirm this in both the browser and in the welcomeHandler.
This is all hosted locally.
I'm not sure if this is a issue with my server, my browser, or if my understanding that cookies are applied by the browser to any future client requests until the cookie expiration date is wrong.
Any help is appreciated!
Browsers default the cookie path to the request path. Set the path to "/" to make the cookie available across the site.
Do not set the domain unless you specifically have a reason to do so (thank you Volker for noting that).
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Path: "/", // <--- add this line
Expires: newSession.TimeOut,
// Domain: "localhost", <-- Remove this line
})

IBM Watson speech to text WebSocket authorization with IAM API key

I am trying to connect to IBM Watson's speech to text WebSocket with a new account, that has IAM authorization only.
My WS endpoint is wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize and I get an access token via https://iam.bluemix.net/identity/token. Now when I open the socket connection with Authorization header with value Bearer token I get a bad handshake response: websocket: bad handshake, Unauthorized 401. Language is Go.
Am I doing something wrong or it is not possible to connect to Watson's speech to text WebSocket without username/password authentication i.e. the deprecated watson-token?
EDIT:
Code to open WebSocket:
headers := http.Header{}
headers.Set("Authorization", "Bearer " + access_token)
conn, resp, err := websocket.DefaultDialer.Dial("wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize", headers)
I have also tried basic authorization with apikey:**api_key** and the result is the same: 401.
EDIT 2:
Code to get the access token (based on the Watson Swift and Python SDKs) which succeeds and returns access and refresh tokens:
func getWatsonToken(apiKey string) (string, error) {
// Base on the Watson Swift and Python SDKs
// https://github.com/watson-developer-cloud/restkit/blob/master/Sources/RestKit/Authentication.swift
// https://github.com/watson-developer-cloud/python-sdk/blob/master/watson_developer_cloud/iam_token_manager.py
const tokenUrl = "https://iam.bluemix.net/identity/token"
form := url.Values{}
form.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey")
form.Set("apikey", apiKey)
form.Set("response_type", "cloud_iam")
// Token from simple "http.PostForm" does not work either
//resp, err := http.PostForm(tokenUrl, form)
req, err := http.NewRequest(http.MethodPost, tokenUrl, nil)
if err != nil {
log.Printf("could not create HTTP request to get Watson token: %+v", err)
return "", nil
}
header := http.Header{}
header.Set("Content-Type", "application/x-www-form-urlencoded")
header.Set("Accept", "application/json")
// "Yng6Yng=" is "bx:bx"
header.Set("Authorization", "Basic Yng6Yng=")
req.Header = header
req.Body = ioutil.NopCloser(bytes.NewReader([]byte(form.Encode())))
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("problem executing HTTP request to get Watson token: %+v", err)
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", errors.New(fmt.Sprintf("failed to get Watson token: %d", resp.StatusCode))
}
jsonBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("problem reading Watson token from response body: %+v", err)
}
tokenResponse := &struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
Expiration int64 `json:"expiration"`
}{}
err = json.Unmarshal(jsonBody, tokenResponse)
if err != nil {
log.Printf("could not parse Watson token response: %+v", err)
return "", err
}
return tokenResponse.AccessToken, err
}
I made an error in the endpoint and used the text-to-speech instead of the speech-to-text. With the correct URL the WebSocket API works.
wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize should be wss://stream-fra.watsonplatform.net/speech-to-text/api/v1/recognize

Resources