Setting secure cookie for python client connection - authentication

I am currently attempting to set a secure cookie for a connection that is incoming from a Python client using Tornado, however, although setting the cookie works fine for connecting incoming from browsers, the set_secure_cookie call does not seem to work in the case of a Python client.
Below are excerpts from my Tornado server code which serves both WebSocket and HTTP Requests:
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user")
class LoginHandler(BaseHandler):
def get(self):
self.write('<html><body><form action="/login" method="post">'
'Name: <input type="text" name="name">'
'<input type="submit" value="Sign in">'
'</form></body></html>')
def post(self):
print("post received: ", self.get_argument("name"))
try:
print('trying to set cookie')
self.set_secure_cookie("user", self.get_argument("name"))
except Exception as e:
print(e)
print("cookie: ", self.get_current_user())
self.redirect("http://192.168.6.21/")
def main():
application = tornado.web.Application([
(r'/ws', EchoWebSocket),
(r'/login', LoginHandler)
], cookie_secret="nescafeh")
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()
And attempting to send a POST request to get the cookie set on a separate client:
s=requests.Session()
r = s.post("http://127.0.0.1:9000/login", data={'name': 'hello'})
sleep(2)
print(r.text)
No errors are returned when trying to set the cookie, removing the 'self.redirect' line to see the response from the POST request does not help (there is no text printed).
Thanks a lot!

r.text is empty because you're not writing anything to the response body in your handler.
Try self.write("something") in your handler's post method and r.text should print out the response.
You can also check r.cookies to see if your cookie is set or not.

Related

Cross Origin Problem with Flask Api (Access-Control-Allow-Origin)

Hello all good people.
I have tested everything that I can find on internet and nothing is working to fix this problem. I'm really hoping that someone here can help me solve this.
When i try to do "patch" request from backend to my flask API I get this error (GET, DELETE & PUT are working fine):
Access to fetch at 'https://MYAPI-NOTREALURL.com' from origin
'https://MYBACKEND-NOTREALURL.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: The
'Access-Control-Allow-Origin' header contains multiple values
'https://MYBACKEND-NOTREALURL.com, *', but only one is allowed. Have
the server send the header with a valid value, or, if an opaque
response serves your needs, set the request's mode to 'no-cors' to
fetch the resource with CORS disabled.
This is how my code for API is written:
from flask_cors import CORS, cross_origin
from flask import render_template, redirect, flash, request, url_for, jsonify, session, make_response
from flask_restful import Api, Resource, reqparse
import requests
app = Flask(__name__)
cors = CORS(app, resources={r"/*": {"origins": "*"}})
api = Api(app)
class ordersByID(Resource):
def get(self,ID_ORDER):
****
return jsonify(data)
def patch(self,ID_ORDER):
req321 = request.form
result = updateOrder(req321,ID_ORDER)
return result
def delete(self,ID_ORDER):
****
return result
def put(self,ID_ORDER):
****
return result
api.add_resource(ordersByID, "/orders/id/<string:ID_ORDER>")
if __name__ == '__main__':
app.run(debug=True)
I have tested everything that I can find on internet and nothing is working when trying to do patch request. I'm doing patch request with fetch from popup window.
<form action="{{ **https://MYAPI-NOTREALURL.com** }}" id="popupForm" method="patch" onsubmit="formFetch(event,this,'patch')">
You can check javascript code under.
function formFetch(e,form,method) {
result = fetch(form.action, {method:method, body: new FormData(form)})
.then(response => response.json())
.then(data => document.getElementById('submitedFormConfirmationText').innerHTML = data['DB_Result']
);
e.preventDefault();
document.getElementById('submitedFormConfirmation').style.display = 'inline';
};
I really hope that someone can help me solve this problem without needing to redo whole code?
I managed to solve this.
For some strange reason "patch" with small letters was working on local but when deployed it did not work.
Changing method from "patch" to "PATCH" solved this problem.

Cannot POST request using service account key file in Python, getting 'Invalid IAP credentials: Unable to parse JWT', '401 Status Code'

I am trying to send a POST request to a Google App Engine service with a JSON body accompanied by an authorization token. I am generating the access token from a local service account key JSON file. The code below is generating a credential but finally the authorization is being rejected. I also tried different ways already. Even tried writing the request in Postman with a Bearer token in the Header, or even as a plain cURL command. But whatever I try, getting a 401 authentication error. I need to make sure whether the problem is in my side or on the other side with the service. Explored every documentation avaliable but no luck.
from google.auth.transport import requests
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
CREDENTIAL_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
CREDENTIALS_KEY_PATH = 'my-local-service-account-key-file.json'
#the example service url I am trying to hit with requests
url = 'https://test.appspot.com/submit'
headers = {"Content-Type": "application/json"}
#example data I am sending with the request body
payload = {
"key1": "value 1",
"key2": "value 2"
}
credentials = service_account.Credentials.from_service_account_file(
CREDENTIALS_KEY_PATH,
scopes=CREDENTIAL_SCOPES
)
credentials.refresh(requests.Request())
authed_session = AuthorizedSession(credentials)
response = authed_session.request('POST',
url,
headers=headers,
data=payload
)
#adding some debug lines for your help
print(response.text)
print(response.status_code)
print(response.headers)
Getting the Output:
Invalid IAP credentials: Unable to parse JWT
401
{'X-Goog-IAP-Generated-Response': 'true', 'Date': 'Mon, 03 May 2021 06:52:11 GMT', 'Content-Type': 'text/html', 'Server': 'Google Frontend', 'Content-Length': '44', 'Alt-Svc': 'h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"'}
IAP expects a JWT(OpenID Connect (OIDC)) token in the Authorization header while your method will attach an access token the the Authorization header instead. Take a look at the below code snippet to make a request to an IAP secured resource.
Your code needs to be something like the following:
from google.auth.transport.requests import Request
from google.oauth2 import id_token
import requests
def make_iap_request(url, client_id, method='GET', **kwargs):
"""Makes a request to an application protected by Identity-Aware Proxy.
Args:
url: The Identity-Aware Proxy-protected URL to fetch.
client_id: The client ID used by Identity-Aware Proxy.
method: The request method to use
('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
**kwargs: Any of the parameters defined for the request function:
https://github.com/requests/requests/blob/master/requests/api.py
If no timeout is provided, it is set to 90 by default.
Returns:
The page body, or raises an exception if the page couldn't be retrieved.
"""
# Set the default timeout, if missing
if 'timeout' not in kwargs:
kwargs['timeout'] = 90
# Obtain an OpenID Connect (OIDC) token from metadata server or using service
# account.
open_id_connect_token = id_token.fetch_id_token(Request(), client_id)
# Fetch the Identity-Aware Proxy-protected URL, including an
# Authorization header containing "Bearer " followed by a
# Google-issued OpenID Connect token for the service account.
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
if resp.status_code == 403:
raise Exception('Service account does not have permission to '
'access the IAP-protected application.')
elif resp.status_code != 200:
raise Exception(
'Bad response from application: {!r} / {!r} / {!r}'.format(
resp.status_code, resp.headers, resp.text))
else:
return resp.text
Note: The above method works with implicit credentials that can be set by running command: export GOOGLE_APPLICATION_CREDENTIALS=my-local-service-account-key-file.json to set the path to your service account in the environment and then run the python code from the same terminal.
Take a look at this link for more info.

Extracting params in traefik.frontend.auth.forward.address service

Summary
I'm trying to set up an authentication passthrough using Traefik's traefik.frontend.auth.forward.address setting. My main web service has the traefik.frontend.auth.forward.address=login.mydomain.com label on the container. Traefik seems to correctly forward incoming requests intended for mydomain.com to login.mydomain.com, but when the login form is submitted, the POST request gets turned into a GET request before it hits the login service, and the parameters of the original POST request seem to be missing. The user can never log in.
Containers
docker run -d \
-l "traefik.frontend.rule=Host:login.mydomain.com; Method:GET, POST" \
-l "traefik.enable=true" \
login-service
docker run -d \
-l "traefik.frontend.rule=Host:mydomain.com" \
-l "traefik.frontend.auth.forward.address=https://login.mydomain.com" \
-l "traefik.frontend.auth.forward.authResponseHeaders=cookie" \
-l "traefik.enable=true" \
web-service
Question
Using auth.forward.address, should I see the parameters from the original POST request in my login service? Since Traefik turns it into a GET request, where in that request should I be looking for the parameters? Or, perhaps I have misconfigured something? Missing a authResponseHeaders flag maybe?
What Works
Requests to mydomain.com show the login form from login-service, with the URL continuing to show mydomain.com; the redirect to login.mydomain.com is happening behind the scenes, which is correct.
I have also tested the login service by itself, and it seems to work. It hosts a form that submits a POST request to the service, before responding with 200 OK and a Set-Cookie header. In fact, when I go to login.mydomain.com directly, I can login, which sets my cookie, and I can go to mydomain.com and skip the login screen.
What Doesn't Work
When submitting the login form, the POST request hits the login-service (as evidenced by the logs in that service) as a GET request and the data in the POST request appears to be gone. Traefik adds an x-forwarded-method set to POST, but I can't find the data in the original POST request. I need the params from my login form to validate them, and they don't appear to be getting through to the login service.
Traefik Configuration
I don't think anything about my Traefik configuration is relevant here, but I'm including it for completeness.
checkNewVersion = true
logLevel = "DEBUG"
defaultEntryPoints = ["https","http"]
sendAnonymousUsage = true
[api]
dashboard = true
debug = true
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true
exposedbydefault = false
[acme]
email = "admin#mydomain.com"
storage = "acme.json"
entryPoint = "https"
OnHostRule = true
[acme.httpChallenge]
entryPoint = "http"
I tracked down Traefik's auth forward code. Sure enough, the request body is not passed downstream to the authentication service; only the headers make it that far. So much for default form submit behavior.
To get around this, I reworked my client-side authentication logic to submit a POST request with the credentials in the header instead of the body, set using XMLHttpRequest.setRequestHeader.
There's one more catch needed to make it work. I need to set cookies client-side using the Set-Cookie header returned from the authentication server, but if the server returns a 200 OK when the login is successful, Traefik will immediately pass along the original request to the user's intended destination -- meaning the Set-Cookie header will never make it to the user. What I did to get around this was return a 418 I'm a teapot when the authentication was successful. This allows the Set-Cookie header to make it back to the user's browser so the user's auth token can be set. The client then automatically reloads the intended page, this time with the correct cookie set, and now the auth server returns a 200 OK if it sees a valid cookie for the requested service.
Here's what the client side code looks like:
<form id="form" method="post" action="/">
Username: <input type="text" name="username" />
Password: <input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
<script>
// Override the default form submit behavior.
// Traefik doesn't pass along body as part of proxying to the auth server,
// so the credentials have to be put in the headers instead.
const form = document.getElementById("form");
form.addEventListener('submit', function(event) {
const data = new FormData(form);
var request = new XMLHttpRequest();
request.open("POST", "/", true);
request.setRequestHeader("Auth-Form", new URLSearchParams(data).toString());
request.withCredentials = true;
request.onload = function(e) {
if (request.status == 418) {
window.location = window.location.href;
} else {
alert("Login failed.");
}
};
request.send(data);
event.preventDefault();
}, false);
</script>
I'm leaving this issue open at least until the bounty runs out because I can't imagine that this is the intended way to do this. I'm hoping someone can weigh in on how traefik.frontend.auth.forward.address is supposed to be used. Or, if someone has used another authentication proxy strategy with Traefik, I'm eager to hear about it.

API Connect 5 - Error attempting to read the urlopen response data

I'm trying to create a REST API from a SOAP Service using IBM API Connect 5. I have followed all the steps described in this guide (https://www.ibm.com/support/knowledgecenter/en/SSFS6T/com.ibm.apic.apionprem.doc/tutorial_apionprem_expose_SOAP.html).
So, after dragging the web service block from palette, ensuring the correctness of endpoint and publishing the API, I have tried to call the API from the browser. Unfortunately, the API return the following message:
<errorResponse>
<httpCode>500</httpCode>
<httpMessage>Internal Server Error</httpMessage>
<moreInformation>Error attempting to read the urlopen response
data</moreInformation>
</errorResponse>
To testing purpose, I have logged the request and I have tried the request on SOAPUI. The service return the response correctly.
What is the problem?
In my case, the problem was in the backend charset (Content-Type: text/xml;charset=iso-8859-1).
For example, backend returns text/xml in German (or French). Api Connect cannot process character ΓΌ. It needs Content-Type: text/xml;charset=UTF-8.
I had a similar issue, in my case was the accept. if you have an Invoke and the content-type or the accept, is not matching the one of the request, or the response that you got, APIC is getting mad.
Please, check if the formats to send (contentType) and receive (accept) are the same of that your API expected. In my case the error occurs because the API returns a String and my default code is configured to receive a JSON body.
//define a JSON-PLAIN TEXT protocol
private HttpEntity<String> httpEntityWithBody(Object objToParse){
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + "xxx token xxx");
headers.set("Accept", MediaType.TEXT_PLAIN_VALUE);
headers.setContentType(MediaType.APPLICATION_JSON);
Gson gson = new GsonBuilder().create();
String json = gson.toJson(objToParse);
HttpEntity<String> httpEntity = new HttpEntity<String>(json, headers);
return httpEntity;
}
//calling the API to APIC...
ParameterizedTypeReference<String> responseType = new
ParameterizedTypeReference<String>(){};
ResponseEntity<String> result =
rest.exchange(builder.buildAndExpand(urlParams).toUri(), HttpMethod.PUT, httpEntityWithBody(myDTO), responseType);
String statusCode = result.getStatusCodeValue();
String message = result.getBody();

create simple api with python to catch post request

I have developed a python plugin which is capable of sending log file in json format
in mm code i have used requests.post(url, data={})
what will be the api structure that catch this data and will be available for
send anywhere with GET request
If you are fairly new to web programming I would suggest using a lightweight framework like Flask. With it you can define custom paths that your server accepts requests on as follows:
from flask import Flask
from flask import request, jsonify
app = Flask(__name__)
log_file = None
#app.route("/api/logfile", methods=['GET', 'POST'])
def post_logfile():
if request.method == 'GET':
if log_file is not None:
return "Log file not instantiated yet", 404
else:
return jsonify(log_file)
elif request.method == 'POST':
log_file = request.form
if log_file is not None:
# log_file variable will have all the information
# from the JSON log file
return "Ok"
else:
return "No data provided", 400
if __name__ == "__main__":
app.run(port=9000)
As you can see, we have a global variable log_file which will be used to store the JSON logfile data, and a function that accepts both POST and GET requests and acts accordingly. If a GET request is sent, it checks if log_file variable is assigned. If so, it returns the log file as a JSON file else it return a 404 error. If a POST request is sent it checks if it has the log file and stores in the log_file variable, making it useful for all subsequent GET requests.
The URL used are:
localhost:9000/api/logfile
And you only need to change the method of the request(e.g. POST or GET)