Kubeflow sdk - error in client.list_experiments() - kubeflow

I'm following the example in docs (https://www.kubeflow.org/docs/components/pipelines/sdk/connect-api/) but am getting an error when trying to access the experiments or runs (when trying to list and/or create them)
I've port-forwarded ml-pipeline-ui:
kubectl port-forward svc/ml-pipeline-ui 3000:80 --namespace kubeflow
The following results in an error:
import kfp
client = kfp.Client(host='http://localhost:3000')
print(client.list_experiments())
The error I get from client.list_experiments():
> ApiException: (500) Reason: Internal Server Error HTTP response
> headers: HTTPHeaderDict({'X-Powered-By': 'Express', 'content-type':
> 'application/json', 'trailer': 'Grpc-Trailer-Content-Type', 'date':
> 'Thu, 22 Jul 2021 21:09:12 GMT', 'x-envoy-upstream-service-time': '7',
> 'server': 'envoy', 'connection': 'close', 'transfer-encoding':
> 'chunked'}) HTTP response body: {"error":"Internal error:
> Unauthenticated: Request header error: there is no user identity
> header.: Request header error: there is no user identity
> header.\nFailed to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).canAccessExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:249\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:148\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357\nFailed
> to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:150\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357","code":13,"message":"Internal
> error: Unauthenticated: Request header error: there is no user
> identity header.: Request header error: there is no user identity
> header.\nFailed to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).canAccessExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:249\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:148\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357\nFailed
> to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:150\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357","details":[{"#type":"type.googleapis.com/api.Error","error_message":"Internal
> error: Unauthenticated: Request header error: there is no user
> identity header.: Request header error: there is no user identity
> header.\nFailed to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).canAccessExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:249\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:148\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357\nFailed
> to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:150\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357","error_details":"Internal
> error: Unauthenticated: Request header error: there is no user
> identity header.: Request header error: there is no user identity
> header.\nFailed to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).canAccessExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:249\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:148\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357\nFailed
> to authorize with API resource
> references\ngithub.com/kubeflow/pipelines/backend/src/common/util.Wrap\n\t/go/src/github.com/kubeflow/pipelines/backend/src/common/util/error.go:275\ngithub.com/kubeflow/pipelines/backend/src/apiserver/server.(*ExperimentServer).ListExperiment\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/server/experiment_server.go:150\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler.func1\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:748\nmain.apiServerInterceptor\n\t/go/src/github.com/kubeflow/pipelines/backend/src/apiserver/interceptor.go:30\ngithub.com/kubeflow/pipelines/backend/api/go_client._ExperimentService_ListExperiment_Handler\n\t/go/src/github.com/kubeflow/pipelines/backend/api/go_client/experiment.pb.go:750\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/grpc#v1.34.0/server.go:871\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357"}]}
However, listing the pipelines works:
import kfp
client = kfp.Client(host='http://localhost:3000')
print(client.list_pipelines())
I have verified that the port-forwarding worked - I am able to access the UI at http://localhost:3000/ and can see the pipelines. Any ideas on where I am going wrong with the experiments? The example in the docs seems pretty straightforward but something is not working.

I received feedback from the developers (see the closed issue). This is one of the current caveats of multi-user mode (see documentation). This usage is now being supported through #5138.

Found How to do programmatic authentication with Dex? #140. By modifying the code as per the solution provided there, it worked.
Need follow the steps at Connect to Kubeflow Pipelines from outside your cluster first to be able to connect via localhost:3000.
import requests
import kfp
import kfp.dsl as dsl
from kfp.components import create_component_from_func
# Does not work
#import kfp
#client = kfp.Client(host='http://localhost:3000', namespace='kubeflow')
#print(client.list_experiments(namespace='kubeflow'))
# --------------------------------------------------------------------------------
# https://github.com/kubeflow/kfctl/issues/140#issuecomment-719894529
# How to do programmatic authentication with Dex? #140
# --------------------------------------------------------------------------------
HOST = "http://localhost:8080/"
USERNAME = "user#example.com"
PASSWORD = "12341234"
NAMESPACE = "kubeflow-user-example-com"
session = requests.Session()
response = session.get(HOST)
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
data = {"login": USERNAME, "password": PASSWORD}
session.post(response.url, headers=headers, data=data)
session_cookie = session.cookies.get_dict()["authservice_session"]
client = kfp.Client(
host=f"{HOST}/pipeline",
cookies=f"authservice_session={session_cookie}",
namespace=NAMESPACE,
)
print(client.list_pipelines())
def add(a: float, b: float) -> float:
'''Calculates sum of two arguments'''
return a + b
add_op = create_component_from_func(
add, output_component_file='add_component.yaml')
#dsl.pipeline(
name='Addition pipeline',
description='An example pipeline that performs addition calculations.'
)
def add_pipeline(
a='1',
b='7',
):
# Passes a pipeline parameter and a constant value to the `add_op` factory
# function.
first_add_task = add_op(a, 4)
# Passes an output reference from `first_add_task` and a pipeline parameter
# to the `add_op` factory function. For operations with a single return
# value, the output reference can be accessed as `task.output` or
# `task.outputs['output_name']`.
second_add_task = add_op(first_add_task.output, b)
# Specify argument values for your pipeline run.
arguments = {'a': '7', 'b': '8'}
# Create a pipeline run, using the client you initialized in a prior step.
#client.create_run_from_pipeline_func(add_pipeline, arguments=arguments)
kfp.compiler.Compiler().compile(
pipeline_func=add_pipeline,
package_path='pipeline.yaml')
Apparently the multi user isolation implementation induced issues, and it seems the documentations under Pipelines SDK have not been updated.

Related

Google IOT core- http bridge "request is missing required authentication credential "

I'm trying to get started with google IOT core by posting a simple http request from the command line.
I have set up my registry and device in Console, and added the public key. I set up a telemetry topic. I've generated the JWT using a Qt application I found, using the private key. I'm using the procedure specified at https://cloud.google.com/iot/docs/how-tos/http-bridge. My command is:
curl -X POST -H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJzeWx2YW4tam91cm5leS0xOTU4MTUiLCJleHAiOiIxNTIwMzU4NjMyIiwiaWF0IjoiMTUxOTc1MzgzMiJ9.kDkwtWvfAE+AOYT2cObgh8Mux2n1DOuek1KR0YrsFSI=' -H 'content-type: application/json' --data '{"binary_data": "SGVsbG8="}' -H 'cache-control: no-cache' 'https://cloudiotdevice.googleapis.com/v1/projects/sylvan-journey-195815/locations/europe-west1/registries/MyDeviceRegistry/devices/FirstDevice:publishEvent'
When I try to post the command I get error 401 "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential"
I don't know where to look. Is there a problem with my JWT? Is the format of the command wrong? Do I need to add a public key to the registry or just to the devices. How do I find out what's wrong?
Any guidance much appreciated
A few ideas:
(Update) Check the JWT is valid on JWT.io
Regenerate your EC public / private key and register the device again
Note the maximum lifetime of a token is 24 hours.
Make sure that your device was registered with the correct credentials, region, and Cloud project.
Ensure that HTTP is enabled for your registry
How did you register your device? If the device was registered with a certificate that has expired, you could encounter authentication issues.
The following Python code is how I generate JWTs from the commandline for Curl-testing the HTTP endpoint assuming an RSA256 key:
import datetime
import jwt
import requests
algorithm = 'RS256'
cloud_region = 'your-cloud-region'
device_id = 'your-device-id'
private_key_file = 'path/to/rsa_private.pem'
project_id = 'your-project-id'
registry_id = 'your-registry-id'
token = {
# The time the token was issued.
'iat': datetime.datetime.utcnow(),
# Token expiration time.
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60),
# The audience field should always be set to the GCP project id.
'aud': project_id
}
# Read the private key file.
with open(private_key_file, 'r') as f:
private_key = f.read()
print(jwt.encode(token, private_key,
algorithm=algorithm).decode('ascii'))
The following image shows you the setting in the Cloud Console for enabling HTTP/MQTT that can be found under IoT Core > Registry > Edit Registry. Note that if you disable HTTP, you will not be able to use the HTTP device bridge.

Why does API Gateway not have permissions for my Authorizer lambda when using Swagger?

I have an API defined using Swagger, which defines the following API Key authorizer:
securityDefinitions:
api_key:
type: apiKey
name: x-api-key
in: header
x-amazon-apigateway-authtype: "oauth2"
x-amazon-apigateway-authorizer:
type: token
authorizerUri: arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:[accountid]:function:ApiKeyAuthorizerLambdaFunction/invocations
authorizerResultTtlInSeconds: 0
However, when I pass this Swagger definition into my CloudFormation script and call an endpoint secured with this authorizer, I get a 500. The API Gateway logs show the following:
Incoming identity: ***key
Execution failed due to configuration error: Invalid permissions on Lambda function
Execution failed due to configuration error: Authorizer error
Note that I have given API Gateway permission to execute this lambda:
LambdaPermissionAuthorizerApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt:
- ApiKeyAuthorizerLambdaFunction
- Arn
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
In fact, when I compare the CloudFormation script which Serverless generates for authoized endpoints (using Serverless's notation) with my own CloudFormation script using Swagger, I see little difference between them, except that my authorizer is defined with Swagger rather than directly as a CF resource.
Can anyone shed any light on this? Is this a bug when using Swagger with CloudFormation?
Can you try setting the authorizerCredentials parameter of your x-amazon-apigateway-authorizer to a valid IAM role that has permission to execute the authorizer lambda? Not sure the standard AWS::Lambda::Permission applies for this, though you probably want to keep it for now just in case it is still required.
The x-amazon-apigateway-authorizer docs show an example.

Unsupported grant type in Google OAuth

I am getting an error "unsupported grant type" when I try to request an OAuth token for a service account using curl. I'm following the example for OAuth 2.0 for service accounts (https://developers.google.com/identity/protocols/OAuth2ServiceAccount) and I think I have everything setup correctly. I have a service account setup in Google Cloud and I'm using that email address in the OAuth request.
The documentation says to use the URL encoded grant type "urn:ietf:params:oauth:grant-type:jwt-bearer" but it isn't clear if this is the only option for the grant type or what other options might be.
I am sending the the base64 encoded header
{"alg":"RS256","typ":"JWT"}
and "."
and base64 encoded claims
{
"iss":"chargepubadmin#xxxxxxxx.iam.gserviceaccount.com",
"scope":"https://www.googleapis.com/auth/pubsub",
"aud":"https://www.googleapis.com/oauth2/v4/token",
"exp":1497159875,
"iat":1497156275
}
and "."
and base64 encoded signature
{base64 header}.{base64 claims}
.
curl -X POST -d 'grant_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fdevice%2F1.0%26assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaXNzIjoiY2.......' "https://www.googleapis.com/oauth2/v4/token"
I'm using an online base64 encoding tool which matches the example base64 encoding.
Can anyone enlighten me as to the what the grant type is or should be?
The grant type should be set as urn:ietf:params:oauth:grant-type:jwt-bearer documented here under the REST API Making the access token request section.
Working example using google-auth library
It will be very easy and simple, if you used the google-auth library which automatically takes care of parsing the private key json file, fetching access tokens, refreshing them and actually including them as part of the requests.
You only need to provide the request URL and body, the library takes care of the rest. Here is a simplified example:
#!/usr/bin/env python
from google.auth.transport.requests import AuthorizedSession
from google.oauth2.service_account import Credentials
# BEGIN CONFIGURATION - change as needed.
# Path to the JSON file containing the service account private key and email.
PRIVATE_KEY_JSON = '/path/to/json/file'
# The API scope this token will be valid for.
API_SCOPES = ['https://www.googleapis.com/auth/pubsub']
# END CONFIGURATION
if __name__ == '__main__':
credentials = Credentials.from_service_account_file(
PRIVATE_KEY_JSON, scopes=API_SCOPES)
authed_session = AuthorizedSession(credentials)
url = 'https://pubsub.googleapis.com/v1/<SOMETHING>'
response = authed_session.get(url)
print str(response.content)
Working example without additional libraries
If you do not want to use any additional libraries but can use the standard python libraries, here is a working sample (tested personally with a service account of my own) in Python (supports both 2.x and 3.x versions) which takes care of all the steps:
#!/usr/bin/env python
import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA256 as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import json
import time
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
# BEGIN CONFIGURATION - change as needed.
# Path to the JSON file containing the service account private key and email.
PRIVATE_KEY_JSON = '/path/to/json/file'
# The API scope this token will be valid for.
API_SCOPE = 'https://www.googleapis.com/auth/pubsub'
# The validity of the token in seconds. Max allowed is 3600s.
ACCESS_TOKEN_VALIDITY_SECS = 3600
# END CONFIGURATION
class OauthAccessTokenGetter:
"""Fetches a new Google OAuth 2.0 access token.
The code is based on the steps described here: https://developers.go
ogle.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
"""
ACCESS_TOKEN_AUD = 'https://www.googleapis.com/oauth2/v4/token'
REQUEST_URL = 'https://www.googleapis.com/oauth2/v4/token'
GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
def __init__(self, private_key_json_file, scope, token_valid_secs=3600):
self.private_key_json = self.LoadPrivateKeyJsonFromFile(
private_key_json_file)
self.scope = scope
self.token_valid_secs = token_valid_secs
#classmethod
def Base64UrlEncode(cls, data):
"""Returns the base64url encoded string for the specified data."""
return base64.urlsafe_b64encode(data)
#classmethod
def LoadPrivateKeyJsonFromFile(cls, private_key_json_file):
"""Returns JSON object by parsing the specified private key JSON
file."""
with open(private_key_json_file) as private_key_json_file:
return json.load(private_key_json_file)
def GetPrivateKey(self):
"""Returns the imported RSA private key from the JSON data."""
return RSA.importKey(self.private_key_json['private_key'])
def GetSigner(self):
"""Returns a PKCS1-V1_5 object for signing."""
return PKCS1_v1_5.new(self.GetPrivateKey())
#classmethod
def GetEncodedJwtHeader(cls):
"""Returns the base64url encoded JWT header."""
return cls.Base64UrlEncode(json.dumps({'alg': 'RS256', 'typ': 'JWT'}).encode('utf-8'))
def GetEncodedJwtClaimSet(self):
"""Returns the base64url encoded JWT claim set."""
current_time_secs = int(time.time())
jwt_claims = {
'iss': self.private_key_json['client_email'],
'scope': self.scope,
'aud': self.ACCESS_TOKEN_AUD,
'exp': current_time_secs + self.token_valid_secs,
'iat': current_time_secs
}
return self.Base64UrlEncode(json.dumps(jwt_claims).encode('utf-8'))
def GetJwtSignature(self, message):
"""Returns signature of JWT as per JSON Web Signature (JWS) spec."""
signed_message = self.GetSigner().sign(SHA.new(message))
return self.Base64UrlEncode(signed_message)
def GetSignedJwt(self):
"""Returns signed JWT."""
header = self.GetEncodedJwtHeader()
jwt_claim_set = self.GetEncodedJwtClaimSet()
signature = self.GetJwtSignature(header + b'.' + jwt_claim_set)
return header + b'.' + jwt_claim_set + b'.' + signature
def SendRequest(self, body):
"""Returns the response by sending the specified request."""
return urlopen(self.REQUEST_URL, urlencode(body).encode('utf-8')).read()
def GetAccessToken(self):
"""Returns the access token."""
body = {
'grant_type': self.GRANT_TYPE,
'assertion': self.GetSignedJwt()
}
response = json.loads(self.SendRequest(body))
return response['access_token']
if __name__ == '__main__':
print (OauthAccessTokenGetter(PRIVATE_KEY_JSON, API_SCOPE,
ACCESS_TOKEN_VALIDITY_SECS).GetAccessToken())
After you get the access token, you need to include it as the Bearer header in the requests you send as described here.
GET /drive/v2/files HTTP/1.1
Authorization: Bearer <access_token>
Host: www.googleapis.com/
Equivalently in curl as:
curl -H "Authorization: Bearer <access_token>" https://www.googleapis.com/drive/v2/files
Although it is described here that you can specify the token using access_token= parameter, I could not get it working at least for Google Compute Engine APIs, may be it works with PubSub, but the Bearer header approach has worked always in my experience.
UPDATE: As per the discovery doc for PubSub API, there seems to be a query parameter for access_token=, so it might very well work too.
"access_token": {
"description": "OAuth access token.",
"type": "string",
"location": "query"
},
And the discovery doc for Compute Engine APIs indicate the use of oauth_token query parameter instead and I did verify that it worked.
"oauth_token": {
"type": "string",
"description": "OAuth 2.0 token for the current user.",
"location": "query"
},

PassportJS + Freshbooks - OAuth errors

I'd like to use PassportJS with Freshbooks.com.
Freshbooks uses OAuth 1.0a, so I copied over the passport-linkedin repo and attempted to convert it to Freshbooks.
I'm getting an error I don't understand:
failed to obtain request token (status: 400 data: Unsupported signature method specified.)
Is there a debug switch for Passport? I also put together another version using just the OAuthStrategy and I'm getting the same error.
The Freshbooks OAuth API is here: http://developers.freshbooks.com/authentication-2/#OAuth
To run the example server in the module:
git clone git#github.com:MichaelJCole/passport-freshbooks.git
npm install
npm install passport express ejs passport-oauth
node example/login/app.js
The stack trace:
failed to obtain request token (status: 400 data: Unsupported signature method specified.)
at /home/michaelcole/scm/passport-freshbooks/node_modules/passport-oauth/lib/passport-oauth/strategies/oauth.js:196:36
at /home/michaelcole/scm/passport-freshbooks/node_modules/passport-oauth/node_modules/oauth/lib/oauth.js:518:17
at passBackControl (/home/michaelcole/scm/passport-freshbooks/node_modules/passport-oauth/node_modules/oauth/lib/oauth.js:374:13)
at IncomingMessage.<anonymous> (/home/michaelcole/scm/passport-freshbooks/node_modules/passport-oauth/node_modules/oauth/lib/oauth.js:386:9)
at IncomingMessage.EventEmitter.emit (events.js:117:20)
at _stream_readable.js:910:16
at process._tickCallback (node.js:415:13)
Ok, this looks like it happened because the server wanted PLAINTEXT encoding vs HMAC-SHA1
The solution was to update the strategy to include signatureMethod
function Strategy(options, verify) {
options = options || {};
options.requestTokenURL = 'https://' + options.serverName + '/oauth/oauth_request.php';
options.accessTokenURL = 'https://' + options.serverName + '/oauth/oauth_access.php';
options.userAuthorizationURL = 'https://' + options.serverName + '/oauth/oauth_authorize.php';
options.signatureMethod = "PLAINTEXT"; // < ------------------------ HERE
options.sessionKey = options.sessionKey || 'oauth:freshbooks';
console.log(options.requestTokenURL);

OAuth 2.0 Using for Google Login: Getting Unknown Host Exception

I am trying to implement Google Login for my webapp Using OAuth 2.0 for Login. However, while performing the step 4 ie. "Exchange Code for Access Token and ID Token", when I perform a POST request for the access and ID token, I get the following exception along with the below stacktrace.
java.net.UnknownHostException: https://accounts.google.com/o/oauth2/token
at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$1.lookupAllHostAddr(InetAddress.java:866)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1258)
at java.net.InetAddress.getAllByName0(InetAddress.java:1211)
at java.net.InetAddress.getAllByName(InetAddress.java:1127)
at java.net.InetAddress.getAllByName(InetAddress.java:1063)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.resolveHostname(DefaultClientConnectionOperator.java:242)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:130)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:150)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:575)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:776)
at dispatch.classic.BlockingHttp$class.dispatch$classic$BlockingHttp$$execute(Http.scala:45)
at dispatch.classic.BlockingHttp$$anonfun$execute$1$$anonfun$apply$3.apply(Http.scala:58)
at dispatch.classic.BlockingHttp$$anonfun$execute$1$$anonfun$apply$3.apply(Http.scala:58)
at scala.Option.getOrElse(Option.scala:120)
at dispatch.classic.BlockingHttp$$anonfun$execute$1.apply(Http.scala:58)
at dispatch.classic.Http.pack(Http.scala:25)
at dispatch.classic.BlockingHttp$class.execute(Http.scala:53)
at dispatch.classic.Http.execute(Http.scala:21)
at dispatch.classic.HttpExecutor$class.x(executor.scala:36)
at dispatch.classic.Http.x(Http.scala:21)
at dispatch.classic.HttpExecutor$class.when(executor.scala:50)
at dispatch.classic.Http.when(Http.scala:21)
at dispatch.classic.HttpExecutor$class.apply(executor.scala:60)
at dispatch.classic.Http.apply(Http.scala:21)
Following are the details of the POST request:
import dispatch.classic._
val req = :/("https://accounts.google.com/o/oauth2/token").secure
val params = Map(
"code" -> code,
"client_id" -> googleClientId,
"client_secret" -> googleClientSecret,
"redirect_uri" -> googleAuthURI,
"grant_type" -> "authorization_code"
)
val res = parse(h(req << params as_str))
where,
code is the auth code returned in the pervious request to https://accounts.google.com/o/oauth2/auth [Step 2 over here ]
googleAuthURI is "https://localhost/portal/google/login" which is what I have specified in the Google API developer console.
Also I am using scala and dispatch to implement this.
Turns out it was a problem with how dispatch finds out the hostname from the request url. In this case
val req = :/("https://accounts.google.com/o/oauth2/token").secure
Dispatch interprets the hostname as "https://accounts.google.com/o/oauth2/token" which is not available and hence it throws an exception. To counter this we need to structure the req url as,
val req = :/("accounts.google.com"/"o"/"oauth2"/"token").secure

Resources