We have a python web server app based on connexion and Flask libraries.
For audit purposes we print every request, including request body, to the logs. Those logs are much more extensive than what Flask prints by default.
However, there are parameters like passwords or keys that I don't want to print their values to the logs.
I want to add custom attribute to several parameters' definition in swagger.yml so on web app start up I will get parameters definitions from connexion and save all the parameters that have this custom attribute, so later on I will hide values of those parameters in the logs.
My desired definition:
paths:
/demo_add_user:
post:
summary: "add user"
operationId: api.api.demo_add_user
parameters:
- name: "new_user"
in: body
required: true
description: "Use to add"
schema:
$ref: "#/definitions/NewUser"
- name: "password"
in: body
description: "user password"
required: false
type: string
x-hidden: true
responses:
Obviously, I would like connexion to ignore this attribute since this is specific to our implementation.
Any way to add custom attribute like x-hidden to parameter definition?
The solution is to use OpenApi extensions by adding x-attr like attribute.
The details are in next link:
https://swagger.io/specification/#specificationExtensions
Just extending #D'artanian's answer :
using nodejs, for example it may be :
swagger :
/liveness:
get:
operationId: apiHealthLiveness
x-myCustomParam : "test"
summary: Liveliness test for service
description: deterimines if a service is still alive
responses:
"200":
...
And using req object, access it :
const myCustomParamValue = req.swagger.operation["x-myCustomParam"];
Related
1.HTTP python client (a script) => 2. GCP API gateway => 3. validate request against firebase => 4. if request valid call cloud function v2 (cloud run)
Python script is generating ID token from refresh token by using 'https://securetoken.googleapis.com/v1/token?#key=myKey And doing a request to API gateway using it.
API gateway config is al follow.
swagger: "2.0"
info:
title: Test
description: API to read validated token details
version: 1.0.0
paths:
/test:
get:
x-google-backend:
address: URL TO cloud function
responses:
"200":
schema:
$ref: '#/definitions/UserDetails'
description: Successful response
description: Returns details.
operationId: testID
summary: Get details from xyz
security:
- firebase: []
.
.
.
definitions:
UserDetails:
title: Root Type for UserDetails
description: User details object created from information in headers
type: object
properties:
userId:
type: string
email:
type: string
securityDefinitions:
firebase:
flow: implicit
authorizationUrl: ''
scopes: {}
type: oauth2
x-google-issuer: "https://securetoken.google.com/*********"
x-google-jwks_uri: "https://www.googleapis.com/***"
x-google-audiences: "******"
This is working fine. I want to make this HTTP python client (a script) public. But i think it is not safe to expose https://securetoken.googleapis.com/v1/token?#key=myKey URL which is getting used to generate idToken from refreshToken(User will use his refresh token from our website).
How do i make my HTTP python client (a script) public securely ?
They can be exploited to access the resources they allow access to, it is dangerous to make your refresh token and API key available to the general public. Instead of having the Python script make a direct connection to the https://securetoken.googleapis.com/v1/token endpoint, you may have the script make a request to a server-side application under your control, which then performs the API call and delivers the ID token to the client.
To make your HTTP Python client public securely, use SSL encryption, validate incoming requests, limit access to sensitive information, monitor for security threats, and keep your software up to date.
You may want to check this documentation to use the best practices when using SSL encyption.
I am writing a python script to add a user(an existing user from the AAD backed provider) to Azure DevOps. I am using python client library of Azure DevOps for this purpose.
After authentication, I am able to fetch the users from azure devops as:
# Create a connection to the org
credentials = BasicAuthentication('', personal_access_token)
connection = Connection(base_url=organization_url, creds=credentials)
# Get a client (the "graph" client provides access to list,get and create user)
graph_client = connection.clients_v5_0.get_graph_client()
resp = graph_client.list_users()
# Access the properties of object as object.property
users = resp.graph_users
# Show details about each user in the console
for user in users:
pprint.pprint(user.__dict__)
print("\n")
How to add a user using this GraphClient connection?
There is a create_user function ( use as graph_client.create_user() ) here to do this: https://github.com/microsoft/azure-devops-python-api/blob/dev/azure-devops/azure/devops/v5_0/graph/graph_client.py
It says that the request should include a GraphUserCreationContext as an input parameter.
But how can I get that GraphUserCreationContext for an AAD user? I only have information about the AAD user's UPN as input.
Note:
I found .NET sample to do this here : https://github.com/microsoft/azure-devops-dotnet-samples/blob/master/ClientLibrary/Samples/Graph/UsersSample.cs
It uses GraphUserPrincipalNameCreationContext which extends GraphUserCreationContext.
But i couldn't find such a class in python client library. I used the code like this:
addAADUserContext = GraphUserCreationContext('anaya.john#domain.com')
print(addAADUserContext)
resp = graph_client.create_user(addAADUserContext)
print(resp)
But got an error:
azure.devops.exceptions.AzureDevOpsServiceError: VS860015: Must have exactly one of originId or principalName set.
GraphUserCreationContext class from the python client for azure devops REST API accepts only one input parameter which is StorageKey. Hence, whatever you provide as an input parameter to that function, be it a UPN or ID, it is set as a storage key.
If you print the addAADUserContext object, you will get:
{'additional_properties': {}, 'storage_key': 'anaya.john#domain.com'}
But the create_user() function of Graph client needs exactly one of originId or principalName set in the GraphUserCreationContext it takes as input parameter.
As the microsoft documentaion for the azure devops REST API (https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/users/create?view=azure-devops-rest-4.1 ) :
The body of the request must be a derived type of GraphUserCreationContext:
GraphUserMailAddressCreationContext
GraphUserOriginIdCreationContext
GraphUserPrincipalNameCreationContext
We shouldn't use the GraphUserCreationContext object directly. But the classes like GraphUserPrincipalNameCreationContext aren't currently available in the python client API. They are working on it. You can track the issue here in GitHub repo: https://github.com/microsoft/azure-devops-python-api/issues/176
You can use User Entitlements - Add REST API for azure devops instead of it's Graph API. You can use the following python client for this purpose:
https://github.com/microsoft/azure-devops-python-api/tree/dev/azure-devops/azure/devops/v5_0/member_entitlement_management
You can refer to the sample given in the following question to know about how to use the mentioned python client :
Unable to deserialize to object: type, KeyError: ' key: int; value: str '
I created this class using their Model
class GraphUserAADCreationContext(Model):
"""
:param principal_name: The principal name from AAD like 'user#mydomain.com'
:type principal_name: str
:param storage_key: Optional: If provided, we will use this identifier for the storage key of the created user
:type storage_key: str
"""
_attribute_map = {
'storage_key': {'key': 'storageKey', 'type': 'str'},
'principal_name': {'key': 'principalName', 'type': 'str'}
}
def __init__(self, storage_key=None, principal_name=None):
super(GraphUserAADCreationContext, self).__init__()
self.storage_key = storage_key
self.principal_name = principal_name
You can us instance of this class as input parameter instead of GraphUserCreationContext
I'm trying to document an already existing python API with Swagger. I wrote the swagger.yaml with every route documented with the help of their editor. Now i would like to deploy the documentation using connexion.
(brief example of the swagger.yaml file)
swagger: "2.0"
info:
description: "This is a description of the routes of the API"
version: "1.0.0"
title: "API"
basePath: "/api"
paths:
/home:
get:
tags:
- "API"
summary: "Home of the application"
operationId: home
responses:
200:
description: "Success"
schema:
type: object
properties:
user_id:
type: string
username:
type: string
403:
description: "Limit of api connections overrun"
I changed the Flask app by a connexion.app during the launch of the server, and was able to specify the .yaml file. But when i'm trying to launch it, it crashes instantly:
File "/usr/local/lib/python2.7/dist-packages/connexion/utils.py", line 74, in get_function_from_name
raise ValueError("Empty function name")
exceptions.ValueError: Empty function name
From my understanding connexion will base it's manual testing feature from the object operationId in every route that needs to point on the function handling the request.
Problem: every route of the API are defined as nested function.
def add_routes(app, oauth):
#app.route('/api/home', methods=['GET'])
#oauth.require_oauth()
def home():
user = request.oauth.user
return jsonify(
user_id=user.user_id,
username=user.username
)
I know nested functions in python are not actually functions at all: not callable, just present in the language in order for us programmers to organize our code.
I think that would be the issue with connexion, it is just not capable of finding these functions and map them for the manual testing feature, but i'm not sure how to fix this. Do you see something that would allow connexion to map the function without having to refactor the entire API in order not to have nested functions ?
Thanks a lot for any help.
My guess is that you haven't defined your handler function. You need to provide a module+function that matches the operationId in the spec.
For example:
I have a function called get_user() in a file app.py , I need to set the operationId to app.get_user.
operationId: app.get_user
Hope that helps!
In the python standard environment quickstart, the endpoints method test_api_key returns a 503 Service Unavailable. The error occurs in the API Explorer when run with dev_appser.py and when the API is deployed. The code for it is:
import endpoints
from protorpc import message_types
from protorpc import messages
from protorpc import remote
class TestResponse(messages.Message):
content = messages.StringField(1)
#endpoints.api(name='practice', version='v1', description='My Practice API')
class PracticeApi(remote.Service):
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key')
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key'))
api = endpoints.api_server([PracticeApi])
I don't have a good understanding of .get_unrecognized_field_info('key') so I am not sure what the issue is? Thanks.
Firstly, I recommend reading Google Protocol RPC Library Overview, since it's Google Cloud Endpoints uses it extensively.
#endpoints.method allows you to configure a specific method in your API. Configuration options are documented in Google Cloud Platform documentation Creating an API with Cloud Endpoints Frameworks for App Engine, in the section, Defining an API method (#endpoints.method).
If you're restricting access to the test/getApiKey/test_api_key method, then you must configure the method with the api_key_required=True option. Restricting API Access with API Keys (Frameworks) discusses that further, but your method annotation should be:
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key',
api_key_required=True
)
Notice your method accepts a request parameter representing the HTTP request (i.e. client using your API):
def test_api_key(self, request):
However, the request parameter is actually Google Protocol RPC Message (Proto RPC) Message object and as such is very well defined. If additional fields exist in the ProtoRPC request parameter, beyond what is formally defined, they are still stored with the request object but must be retrieved using the following method:
def get_unrecognized_field_info(self, key, value_default=None,
variant_default=None):
"""Get the value and variant of an unknown field in this message.
Args:
key: The name or number of the field to retrieve.
value_default: Value to be returned if the key isn't found.
variant_default: Value to be returned as variant if the key isn't
found.
Returns:
(value, variant), where value and variant are whatever was passed
to set_unrecognized_field.
"""
Message class code on GitHub is quite well documented. .
No arguments will appear in the body of a request because you've configured the method with to be called with HTTP GET:
http_method='GET'
...you're correctly using the value message_types.VoidMessage.
In terms of your error, 503 is just a generic server error, can you provide any information from the StackDriver logs? They will point you to the exact line and error in your code.
There were three things that were creating the 503 error.
Firstly, I needed to make the method or entire Api require an Api Key. In this case I just applied it to the entire Api:
#endpoints.api(name='practice', version='v1', api_key_required=True)
class PracticeApi(remote.Service):
Secondly, after I generated the Api Key in the cloud console I needed to put the Key into the openapi.json file before deploying it.
Lastly, I was still getting a validation error:
ValidationError: Expected type <type 'unicode'> for field content, found (u'My Api Key', Variant(STRING, 9)) (type <type 'tuple'>)
The get_unrecognized_field_info() function returns a tuple of (value, variant). A tuple was not expected by the response so I updated the method to only show value:
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key')[0])
editied original question:
Im trying to make a google appengine app which uses the g+ avatar and human name...
...
So it seems that i need the google-api-python-client library in my app.
...
to enable access to the profile scope so i can look up 'me' and grab the users name and avatar and chuck them in a couple of properties in my user objects (with a button to reload the values again or something).
So has anyone does this? Or has a working example (or even a pointer to which of the ways to authorise my app for scope=[profile])?
Discoveries:
I dont need the google-api-python-client library for this. The simple approach was to do the g+ access in pure js on the client and then lookup and push the results to my appengine app. It isnt as secure as doing via the backend, but it is only for displayname and icon (which can be set manually anyway).
I did need to make some other tweaks to make it work though...
following this workflow:
https://developers.google.com/+/web/signin/javascript-flow
Important things to note:
step1 should also state that you MUST fill out "APIs & auth" -> "Consent screen" field "PRODUCT NAME" and "EMAIL ADDRESS" or you get weird errors
You (might) have to do this before you generate the credential (or delete and recreate it)
(credit to answer: Error: invalid_client no application name)
set meta google-signin-scope to "profile" (or maybe "email")
remove the meta header for google-signin-requestvisibleactions (otherwise i got a frame sameorigin error)
obviously the button line from step4 needs to go after the body tag in your document
skip step2, the code from step2 is also included in step4
also on the workflow page, the 'working example' button on that page does not work (dont try it)
Once i did that I could put the following in the successful callback code and do a lookup:
gapi.client.load('plus','v1', function(){
var request = gapi.client.plus.people.get({ 'userId': 'me' });
request.execute(function(resp) {
console.log('Retrieved profile for:' + resp.displayName);
console.log(resp);
console.log(resp.result);
console.log(resp.result.displayName);
console.log(resp.result.image);
});
});
you can see here full example on how to use client library
https://code.google.com/p/google-api-python-client/source/browse/samples/plus/plus.py
i see a snippet of the code stating
try:
person = service.people().get(userId='me').execute()
print 'Got your ID: %s' % person['displayName']
https://developers.google.com/+/api/latest/people#resource
so basically person['image']['url'] will be your path to user's avatar.
full folder: https://code.google.com/p/google-api-python-client/source/browse/samples/plus/