I am doing a RESTful web service by CherryPy. Since the client is not always a browser and may not be able to store cookie, so I plan to get CherryPy's session id and pass it via HTTP GET/POST as a token. When client send request with this token(session id) to CherryPy it could restore session just like what cookie does and the server side can get authentication or any stateful data.
My question is, how to restore the CherryPy session by specific id?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cherrypy.lib
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 4
}
}
class App:
#cherrypy.expose
#cherrypy.config(**{'tools.sessions.on': True})
def counter(self):
if 'counter' not in cherrypy.session:
cherrypy.session['counter'] = 0
cherrypy.session['counter'] += 1
return 'Counter: {0}<br/>Session key: {1}'.format(
cherrypy.session['counter'],
cherrypy.request.cookie['session_id'].value
)
#cherrypy.expose
def recreate(self, token):
'''You can try open it from another browser
once set the value in /counter
'''
cherrypy.request.cookie['session_id'] = token
cherrypy.lib.sessions.init()
# now it should be ready
return 'Counter: {0}'.format(cherrypy.session['counter'])
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
Inspired by saaj I found a solution that works. Try this...
import cherrypy.lib
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 4,
'tools.sessions.on': True,
'tools.sessions.storage_type': "file",
'tools.sessions.storage_path': "adf"
}
}
class Helloworld:
#cherrypy.expose
def make(self, token=''):
'''You can try open it from another browser
once set the value in /counter
'''
if(token == ''):
cherrypy.lib.sessions.init()
# take this token and put in the url 127.0.0.1:8080/make/ + token
return cherrypy.session.id
else:
#send two requests with the token. 1. to set a session var
# and 2. to retrieve the var from session
cherrypy.lib.sessions.init(self, id=token)
print('do something')
# on the second request check the value after init and it's HI!
cherrypy.session['something'] = 'HI'
return token
if __name__ == '__main__':
cherrypy.quickstart(Helloworld(), '/', config)
Hope this helps!
The previous examples didn't work for me (at least for file sessions). Maybe because the current version of cherrypy forbids session init for already initialized sessions. What did work, however, is this:
cherrypy.session.id = token
cherrypy.session._load()
I understand this is dirty, since the private method is called, but it's the only way I found.
Related
I have a fastapi application where I use sqlalchemy and stored procedures.
Now I want to test my endpoints like in the documentation
import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..dependencies import get_db
import cx_Oracle
host = 'xxxx'
port = 1111
sid = 'FUU'
user = 'bar'
password = 'fuubar'
sid = cx_Oracle.makedsn(host, port, sid=sid)
database_url = 'oracle://{user}:{password}#{sid}'.format(
user=user,
password=password,
sid=sid,
)
engine = create_engine(database_url, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
app = FastAPI()
init_router(app)
#pytest.fixture()
def session():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
#pytest.fixture()
def client(session):
# Dependency override
def override_get_db():
try:
yield session
finally:
session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
def test_index(client):
res = client.get("/")
assert res.text
assert res.status_code == 200
def test_search_course_by_verid_exist():
response = client.get(
'search', params={"search_query": "1111", "semester": "S2022"})
# course exist
assert response.status_code == 200
I've tried it with creating a new app and/or importing it via getting the app from the main.py
from ..main import app
The method is in my courses router.
#router.get("/search", status_code=status.HTTP_200_OK)
async def search_course(
response: Response,
search_query: Union[str, None] = None,
semester: Union[int, None] = None,
db: Session = Depends(get_db),
):
.....
return response
The index test already failes by returning assert 400 == 200. For the 2nd (test_search_course_by_verid_exist) I'll get
AttributeError: 'function' object has no attribute 'get'
My main has some middleware settings like
app.add_middleware(
SessionMiddleware, secret_key="fastAPI"
) # , max_age=300 this should match Login action timeout in token-settings of a realm
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=settings.ALLOWED_HOSTS,
)
# MIDDLEWARE
#app.middleware("http")
async def check_route(request: Request, call_next):
....
I'm clueless what I'm missing or if things are just different with cx_Oracle
I've tried changing the testclient from fastapi to the starlette one. I've tried not overriding the db and just import the original db settings (which are basically the same). But nothing works.
I'm not sure if this is the proper way to test FastAPI application, https://fastapi.tiangolo.com/tutorial/testing/
Why you didn't declare client as :
client = TestClient(app)
?
Idk if this was the root problem. But naming my fixtures solved the problem and the db connection is working.
conftest.py
#pytest.fixture(name="db_session", scope="session")
def db_session(app: FastAPI) -> Generator[TestingSessionLocal, Any, None]:
Also created the app fixture
#pytest.fixture(name="app", scope="session")
def app() -> Generator[FastAPI, Any, None]:
"""
Create a fresh database on each test case.
"""
_app = start_application()
yield _app
I'm using Grails Spring Security Core and the Grails Spring Security REST plugin and I'm just starting to get things set up. I initialized the plugins with a User class and an Authority class (defaults) and went to write an integration test, following a guide I found on the Grails website.
It said to put the following in an integration test:
def "test a user with the role ROLE_BOSS is able to access /api/announcements url"() {
when: 'login with the sherlock'
RestBuilder rest = new RestBuilder()
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = 'sherlock'
password = 'elementary'
}
}
then:
resp.status == 200
resp.json.roles.find { it == 'ROLE_BOSS' }
}
I went ahead and did something similar and it worked with a bootstrapped User, but when I tried to do the exact same test with a User created in the test method itself, it would fail with a 401 HTTP response code.
The code I'm trying to run:
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
assert User.count == 2
when:
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
resp.status == 200
}
Note that the User.count == 2 assertion passes because there is one User in Bootstrap.groovy and the one create in the test method.
Why does this work and pass with the bootstrapped User without any issues at all but not the one created in the method? Is there a way I can write this integration test so that I can test the /api/login endpoint included in the grails-spring-security-rest plugin in this way?
The User you create in the given section is in a transaction that has not been committed. When you make the REST call, the api/login controller will be run in a new transaction that cannot see your un-committed User.
A few options (there are others)...
Create User in BootStrap.groovy
def init = { servletContext ->
environments {
test {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
}
}
Make REST calls to create the User - assuming you have such functionality
Create User in setup
#Integration
#Rollback
class UserIntSpec extends Specification {
def setup() {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
when:
def response = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
response.status == HttpServletResponse.SC_OK
when:
def token = response.json.access_token
then:
token
}
}
Note: In Grails >= 3.0, setup() is run in a separate transaction and persisted (why it solves your problem) which is not rolled back. Any data will need to be cleaned up manually.
I suggest you read the grails documentation on testing: Integration Testing
I implemented custom authentication, as described in docs
# custom_permissions.py
from rest_framework import authentication
from rest_framework import exceptions
class KeyAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
key = request.META.get('Authorization')
print(key)
if not key:
raise exceptions.AuthenticationFailed('Authentication failed.')
try:
key = ApiKey.objects.get(key=key)
except ApiKey.DoesNotExist:
raise exceptions.AuthenticationFailed('Authentication failed.')
return (key, None)
In my settings:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'api_server.apps.api_v1.custom_permissions.KeyAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
It works as expected during tests:
def test_1(self):
client = APIClient()
client.credentials(X_SECRET_KEY='INVALID_KEY')
response = client.get('/v1/resource/')
self.assertEqual(response.status_code, 403)
self.assertEqual(response.data, {'detail': 'Authentication failed.'})
def test_2(self):
client = APIClient()
client.credentials(X_SECRET_KEY='FEJ5UI')
response = client.get('/v1/resource/')
self.assertEqual(response.status_code, 200)
However when I test with curl and locally running server, there is no X_SECRET_KEY header found in request.META. It is printing None in terminal, while received key is expected.
$ curl -X GET localhost:8080/v1/resource/ -H "X_SECRET_KEY=FEJ5UI"
{'detail': 'Authentication failed.'}
Could you give a hint, what might be a problem with that?
The headers variables are uppercase and prefixed with "HTTP_". This is general to Django, dunno about other languages / frameworks.
See https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/authentication.py#L23 for example.
I currently have set up a developer account on uber and everything was fine running locally on my PC and http://localhost:7000/submit
I can log in and work with the API. This is great for testing out endpoints. However, my end goal is to use the endpoints on my mobile app. So I went to the redirect URL and switched it to https://my_new_url:7000/submit
I'm using Flask for my server and am using SSL in the following manner:
if __name__ == '__main__':
application.run(host='0.0.0.0',port=7000,ssl_context='adhoc')
However, when navigating to my base url I'm given the following error:
ERROR
THE BASE REDIRECT URI DOES NOT MATCH THE REQUESTED REDIRECT
The code for the base url looks as follows:
def get_redirect_uri(request):
"""Return OAuth redirect URI."""
parsed_url = urlparse(request.url)
if parsed_url.hostname == 'localhost':
return 'http://{hostname}:{port}/submit'.format(
hostname=parsed_url.hostname, port=parsed_url.port
)
return 'https://{hostname}/submit'.format(hostname=parsed_url.hostname)
#application.route('/', methods=['GET'])
def signup():
params = {
'response_type': 'code',
'redirect_uri': get_redirect_uri(request),
'scopes': ','.join(config.get('scopes')),
}
url = generate_oauth_service().get_authorize_url(**params)
return redirect(url)
Do I have to have the application whitelisted before I can change the redirect URL or am I mis-configuring something?
As stated in the error response you received from the /v1/oauth2/authorize endpoint, the problem is that the base of the URI you are sending must match the redirect URI used during the registration of your application in the Uber Developers Dashboard.
Also see this answer: https://stackoverflow.com/a/35638160/313113
I'm trying to integrate Sinch into my ROR webapp, and am having some difficulty formatting the signedUserToken to start the sinchClient.
Here is my view, using haml :
#{#signedUserTicket}
%script{src: "//cdn.sinch.com/latest/sinch.min.js", type: "text/javascript"}
= javascript_tag do
$(function(){
$sinchClient = new SinchClient({
applicationKey: 'APP_KEY',
capabilities: {messaging: true, calling: true},
supportActiveConnection: true,
onLogMessage: function(message) {
console.log(message);
},
});
$sinchClient.start({
'userTicket' : "#{#signedUserTicket}",
});
});
And whatever formatting I try to do in the controller, the closest I get to succeeding is :
DOMException [InvalidCharacterError: "String contains an invalid character"
code: 5
nsresult: 0x80530005
location: http://cdn.sinch.com/latest/sinch.min.js:5]
I'd appreciate a little help and would even build a Rubygem for integrating Sinch in Rails if I get the right info and can spare some time.
Cheers,
James
Edit :
I have tried a few modifications and am getting closer (I think).
The problem of InvalidCharacter came from the trailing '='s which apparently don't decode well in Javascript.
My new controller is now :
class SinchController < ApplicationController
skip_before_filter :verify_authenticity_token
before_filter :authenticate_user!
def client
username = current_user.username
applicationKey = "APP_KEY"
applicationSecret = "APP_SECRET_B64"
userTicket = {
"identity" => {"type" => "username", "endpoint" => username},
"expiresIn" => 3600,
"applicationKey" => applicationKey,
"created" => Time.now.utc.iso8601
}
userTicketJson = userTicket.to_json
userTicketBase64 = Base64.strict_encode64(userTicketJson).chop
digest = Digest::HMAC.digest(Base64.decode64(applicationSecret), userTicketJson, Digest::SHA256)
signature = Base64.strict_encode64(digest).chop
#signedUserTicket = (userTicketBase64 + ':' + signature).remove('=')
end
end
But now I'm facing the following error:
POST https://api.sinch.com/v1/instance 500 (Internal Server Error)
client:1 XMLHttpRequest cannot load https://api.sinch.com/v1/instance. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http:// localhost:3000' is therefore not allowed access. The response had HTTP status code 500.
(the space before localhost is due to new user restrictions on SO)
I added Rack::Cors to my rails server to try and allow Cross-domain requests in case it came from my own requests, but whatever configuration I tried, it seems the request never contains the right headers.
Am I misunderstanding CORS requests? Does the problem come from the requests generated by sinch.min.js?
Regards,
James
Error message is due to Firefox base64 decoder can't decode the token, due to symbols (such as #) that are not in the base64 character set. This suggest that the ticket is actually not passed to start(), and this line may be incorrect;
'userTicket' : "#{#signedUserTicket}",
I dont know HAML but shouldnt
'userTicket' : "#{#signedUserTicket}",
be 'userTicket' : #signedUserTicket,