Please help me, I have no idea what is wrong - my get_current_user dependency throws an 422 Unprocessable entity errror.
I am working on authorization and database connection in my project.
I am trying to create a get_user_me endpoint, which will get and return current user.
#router.get("/me")
def get_user_me(current_user = Depends(get_current_user)):
return current_user
get_current_user dependency should return current user, but it throws HTTP422 (Unprocesable entity).
def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if not username:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user = get_user_by_username(db, token_data.username)
if not user:
raise credentials_exception
return user
Everything works fine until the return. The user is a correct model, but when I step over in the debugger (it should go back to the endpoint) it redirects me to closing my localsession (db.close):
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
I am working with debugger, and in the last line (return user) the user is just a normal model.
>>>user
<src.models.users.User object at 0x0000023B377A14E0>
>>>user.id
0
>>>user.username
'olekniemirka'
Response body I get in the api docs:
{
"detail": [
{
"loc": [
"path",
"user_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
Logs I get in the terminal:
INFO: Started server process [12012]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
2022-11-29 20:45:55,319 INFO sqlalchemy.engine.Engine select pg_catalog.version()
2022-11-29 20:45:55,320 INFO sqlalchemy.engine.Engine [raw sql] {}
2022-11-29 20:45:55,322 INFO sqlalchemy.engine.Engine select current_schema()
2022-11-29 20:45:55,322 INFO sqlalchemy.engine.Engine [raw sql] {}
2022-11-29 20:45:55,322 INFO sqlalchemy.engine.Engine show standard_conforming_strings
2022-11-29 20:45:55,323 INFO sqlalchemy.engine.Engine [raw sql] {}
2022-11-29 20:45:55,324 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2022-11-29 20:45:55,329 INFO sqlalchemy.engine.Engine SELECT users.id, users.username, users.name, users.surname, users.email, users.hashed_password
FROM users
WHERE users.username = %(username_1)s
2022-11-29 20:45:55,329 INFO sqlalchemy.engine.Engine [generated in 0.00062s] {'username_1': 'olekniemirka'}
2022-11-29 20:51:20,858 INFO sqlalchemy.engine.Engine ROLLBACK
INFO: 127.0.0.1:49643 - "GET /users/me HTTP/1.1" 422 Unprocessable Entity
I don't get why, but get_user works just fine (it also uses get_current_user dependency), but the current user is not used anywhere.
It's like the assignment of user to the current_user in my first endpoint is breaking the app.
#router.get("/{user_id}", status_code=status.HTTP_200_OK)
def get_user(user_id: int, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user)):
user = crud.get_user(db, user_id)
return user
# crud get user
def get_user(db: Session, id: int):
stmt = select(models.User).where(models.User.id == id)
user = db.scalar(stmt)
return user
Deleting Config class from my pydantic model solved the problem for some reason, but it is a very ugly solution.
class Config:
orm_mode=True
EDIT:
Answer which solved the question by #MatsLindh
You're trying to retrieve /users/me, but have defined user_id to be int. In that case you're trying to coerce me into an integer, and FastAPI is telling you that "no, the value you're giving for user_id is not an integer". The 422 error is related to that issue, not current_user. Since your /{user_id} endpoint was registered first, that takes precedence over the /me endpoint.
Related
I have a Keycloak auth server running in a standalone mode. My requirement is that users should be able to log in with their Google accounts, therefore I added Google IdP following steps in the Keycloak documentation.
Once a new user has successfully logged in with their Google account, the new account should be created and stored in the Postgresql DB. To achieve this I created custom user storage provider following this example. The sample covers only the fetch user details part. To support adding new users, I implement addUser method of org.keycloak.storage.user.UserRegistrationProviderinterface:
#Override
public UserModel addUser(RealmModel realmModel, String s) {
logger.info("create user with username: " + s);
UserEntity userEntity = new UserEntity(s, null, s, s);
em.persist(userEntity);
em.flush();
return new UserAdapter(kcSession, realmModel, model, userEntity);
}
While testing the flow, the Keycloak throws an exception while executing getUserById custom provider method. According to the logs this method is called multiple times. The method looks like this:
#Override
public UserModel getUserById(String id, RealmModel realm) {
logger.info("getUserById: " + id);
String persistenceId = StorageId.externalId(id);
System.out.println("!!! :" + StorageId.keycloakId(model, id));
System.out.println("!!! " + persistenceId);
Query query = em.createNativeQuery(UserStoreQueries.GET_USER_BY_ID);
query.setParameter(1, Long.valueOf(persistenceId));
Object[] result = (Object[]) query.getSingleResult();
if (result == null) {
logger.info("Could not find user by id: " + id);
return null;
}
return new UserAdapter(kcSession, realm, model, prepareUserEntity(result));
}
Before fetching user from DB, the method tries to extract the user Id (which user table primary key) from the composed Keycloak ID key and use it to get user details. The thing is that at the last method call, the extracted ID value is become equal to NULL somehow, however in the previous method calls it wasn't, see below:
14:55:05,276 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) getUserByEmail: kris#gmail.com
14:55:05,328 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) Could not find user by email: kris#gmail.com
14:55:05,336 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) getUserByUsername: kris#gmail.com
14:55:05,342 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) Could not find user by username: kris#gmail.com
14:55:05,343 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) create user with username: kris#gmail.com
14:55:05,519 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) getUserById: f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:9
14:55:05,519 INFO [stdout] (default task-1) !!! :f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:9
14:55:05,520 INFO [stdout] (default task-1) !!! 9
14:55:05,563 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) getUserById: f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:9
14:55:05,563 INFO [stdout] (default task-1) !!! :f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:9
14:55:05,564 INFO [stdout] (default task-1) !!! 9
14:55:05,718 INFO [com.redhat.custom.storage.user.CustomUserStorageProvider] (default task-1) getUserById: f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:null
14:55:05,719 INFO [stdout] (default task-1) !!! :f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:f:1b171a2b-0d7f-42eb-9c93-89fd7c71347b:null
14:55:05,719 INFO [stdout] (default task-1) !!! null
14:55:05,720 ERROR [org.jboss.as.ejb3.invocation] (default task-1) WFLYEJB0034: Jakarta Enterprise Beans Invocation failed on component CustomUserStorageProvider for method public default org.keycloak.models.UserModel org.keycloak.storage.user.UserLookupProvider.getUserById(org.keycloak.models.RealmModel,java.lang.String): javax.ejb.EJBTransactionRolledbackException: For input string: "null"
at org.jboss.as.ejb3#23.0.2.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:219)
at org.jboss.as.ejb3#23.0.2.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:392)
at org.jboss.as.ejb3#23.0.2.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:160)
at org.jboss.invocation#1.6.0.Final//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
at org.jboss.invocation#1.6.0.Final//org.jboss.invocation.InterceptorContext$Invocation.proceed(InterceptorContext.java:509)
at org.jboss.weld.core#3.1.6.Final//org.jboss.weld.module.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:81)
I am stuck with this. Appreciate, if someone can explain me why my user table PK ID value is removed from the Keycloak composite key?
Following the hartle tutorial here: https://www.learnenough.com/action-cable-tutorial#sec-upgrading_to_action_cable
When I get to Step 4, adding ActionCable the chat messages are not transmitted and I get the error:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ? [["LIMIT", 1]]
An unauthorized connection attempt was rejected
here are the relevant files:
room_channel.rb:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
messages controller:
class MessagesController < ApplicationController
before_action :logged_in_user
before_action :get_messages
def index
end
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
message: render_message(message)
message.mentions.each do |mention|
ActionCable.server.broadcast "room_channel_user_# {mention.id}",
mention: true
end
end
end
private
def get_messages
#messages = Message.for_display
#message = current_user.messages.build
end
def message_params
params.require(:message).permit(:content)
end
def render_message(message)
render(partial: 'message', locals: { message: message })
end
end
room.coffee:
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
alert data.content
routes.rb:
Rails.application.routes.draw do
root 'messages#index'
resources :users
resources :messages
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
mount ActionCable.server, at: '/cable'
end
The reference branch works fine on my machine, but I can't get my tutorial branch to use AC.
Update:
Skipping down to Section 5 of the tutorial, I added connection.rb, which had been blank in the tutorial's beginning repo as follows:
connection.rb:
module ApplicationCable
class Connection < ActionCable::Connection::Base
include SessionsHelper
identified_by :message_user
def connect
self.message_user = find_verified_user
end
private
def find_verified_user
if logged_in?
current_user
else
reject_unauthorized_connection
end
end
end
end
And broadcasting seems to work in one direction. I have two tabs open. but only one works to broadcast messages. In the other, the console shows this error:
Error: Existing connection must be closed before opening action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
Connection.prototype.open
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
bind/<
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:201:60
In the logs, with the above connection.rb, the search for null user is gone, showing this:
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vY2hhdC1hcHAvVXNlci8x)
RoomChannel is transmitting the subscription confirmation
RoomChannel is streaming from room_channel
Started GET "/cable" for ::1 at 2018-12-29 08:04:31 -0500
Started GET "/cable/" [WebSocket] for ::1 at 2018-12-29 08:04:31 -0500
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-alive, Upgrade, HTTP_UPGRADE: websocket)
I try to create connection to cable server and subscribe on channel, but I get error with log:
Started GET "/cable" for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Started GET "/cable/" [WebSocket] for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
WebSocket error occurred: wrong number of arguments (given 2, expected 1)
My code:
// order_slots.coffee
jQuery(document).ready ->
//some jquery code that call create_channel function
create_channel = (order_id) ->
App.cable.subscriptions.create {
channel: "OrderSlotsChannel",
order_id: order_id
},
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Data received
Specific channel:
//order_slots_channel
class OrderSlotsChannel < ApplicationCable::Channel
def subscribed
stream_from "order_slots_#{params[:order_id]}_channel"
end
def unsubscribed; end
end
And ActionCable connection:
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.email
end
protected
def find_verified_user
verified_user = env['warden'].user
verified_user || reject_unauthorized_connection
end
end
end
ActionCable::Channel::Base - is just empty. I will appreciate any help. Thanks in advance
I solved this problem. The project used Passenger Phusion as application server and 5.0.x version badly combine with rails 5.1 and action cable. You should update passenger up to 5.1.x
I would like to cancel a SoftLayer device on a certain date in the future and found the following SoftLayer_Billing_Item_Cancellation_Request::createObject.
What would the request url look like and what would the POST parameters look like if I was using json?
Thanks
This is a Rest example can help you:
Cancel Service - rest
To get billing item, please see:
SoftLayer_Virtual_Guest::getBillingItem
Also, this is a Python example:
"""
Cancel a Virtual Guest.
It cancels the resource for a billing Item. The billing item will be cancelled
immediately and reclaim of the resource will begin shortly.
Important manual pages:
http://sldn.softlayer.com/reference/services/SoftLayer_Virtual_Guest/getObject
http://sldn.softlayer.com/reference/services/SoftLayer_Billing_Item/cancelService
License: http://sldn.softlayer.com/article/License
Author: SoftLayer Technologies, Inc. <sldn#softlayer.com>
"""
import SoftLayer.API
from pprint import pprint as pp
# Your SoftLayer API username and key.
API_USERNAME = 'set me'
# Generate one at https://control.softlayer.com/account/users
API_KEY = 'set me'
virtualGuestId = 9923645
client = SoftLayer.Client(
username=API_USERNAME,
api_key=API_KEY,
)
try:
# Getting the billing item id
mask = 'mask.billingItem.id'
cci = client['SoftLayer_Virtual_Guest'].getObject(mask=mask, id=virtualGuestId)
billingItemId = cci['billingItem']['id']
try:
# Canceling the Virtual Guest
result = client['Billing_Item'].cancelService(id=billingItemId)
pp(result)
except SoftLayer.SoftLayerAPIError as e:
pp('Unable to cancel the VSI faultCode=%s, faultString=%s'
% (e.faultCode, e.faultString))
except SoftLayer.SoftLayerAPIError as e:
pp('Unable to get the billing item id from VSI faultCode=%s, faultString=%s'
% (e.faultCode, e.faultString))
Also there are a lot examples in other clients can help you:
Cancel Service - rest
Cancel service - Python
cancel service - php
cancel service-perl
References
SoftLayer_Billing_Item::cancelService
SoftLayer_Virtual_Guest::getBillingItem
SoftLayer_Virtual_Guest::getObject
This may be what are you looking for:
Post URL: https://api.softlayer.com/rest/v3.1/SoftLayer_Billing_Item_Cancellation_Request/createObject.json
Payload:
{
"parameters": [
{
"complexType": "SoftLayer_Billing_Item_Cancellation_Request",
"accountId": 321752,
"notes": "No notes provided",
"items": [
{
"complexType": "SoftLayer_Billing_Item_Cancellation_Request_Item",
"billingItemId": 25849466,
"scheduledCancellationDate": "5/15/2006"
}
]
}
]
}
I hope it helps
Regards
The answer that worked for me is the following:
https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/[deviceID]/getBillingItem.json
https://api.softlayer.com/rest/v3/SoftLayer_Billing_Item/[BillingItemID]/cancelServiceOnAnniversaryDate.json
Both are Get Requests.
I am running into some timing issues with some mocha-cakes test scripts that run against restify services. I'm using the Restify JSON client to issue the calls, which use callbacks rather than promises. I've passed in the done function to my Givens and Whens, so that I can perform the necessary blocking against these async calls, which prevents inconsistent test suite runs (without the dones, it's a tossup which and how many Thens and Ands will pass).
I am moderately skilled with coffescript, and only a novice when it comes to mocha/mocha-cakes, so I am most certainly doing something wrong in my code. Here is an example of a couple of the test cases that are failing:
require 'mocha-cakes'
should = require 'should'
restify = require 'restify'
Feature "Account API",
"In order to have control over structured Account documents",
"as a consumer of investment account information,",
"I need a RESTful service API.", ->
Scenario "GET /account/:userid", ->
client = restify.createJSONClient
url: "http://localhost:8080",
version: "*"
_e1 = null
_r1 = null
_e2 = null
_r2 = null
_d2 = null
# GET non-existent account
Given "I have not yet created the Account", ->
When "I request the Account", (done) ->
client.get "/account/99", (err, req, res, obj) ->
_e1 = err
_r1 = res
done()
err
Then "it should respond with an error", ->
_e1.should.be.ok
And "the status code should be 404", ->
_r1.should.have.status 404
# GET existent account
Given "I have created the Account", (done) ->
client.post "/account", { userId: 1, accountType: 0, accountCategories: [], beneficiaries: [], accountOwner: { firstName: "Test", lastName: "User" } }, (err) ->
done()
err
When "I request the Account", (done) ->
client.get "/account/1", (err, req, res, obj) ->
_e2 = err
_r2 = res
_d2 = obj
done()
err
Then "it should responond with a document", ->
_d2.should.be.ok
And "it should have the userId 1", ->
_d2.userId.should.eql 1
And "it should have an accountOwner property", ->
_d2.accountOwner.should.be.ok
And "the status code should be 200", ->
_r2.should.have.status 200
When I run this, my output is always the following:
c:\Development\Clients\Pensco\AngularJS\Pensco\newaccountwizard.api>mocha
test/AccountAPITests.coffee -r should -R spec --compilers
coffee:coffee-script/register
Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid
◦
- ◊ Given: I have not yet created the Account (pending)
◦
1) When: I request the Account
◦
√ Then: it should respond with an error
◦
√ And: the status code should be 404
◦
2) Given: I have created the Account
◦
3) When: I request the Account
◦
√ Then: it should responond with a document
◦
√ And: it should have the userId 1
◦
√ And: it should have an accountOwner property
◦
√ And: the status code should be 200
6 passing (6s) 1 pending 3 failing
1) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ When: I request the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
2) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ Given: I have created the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
3) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ When: I request the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
Now, I know that my REST calls via client.get/client.post occur almost instantaneously. When I remove the dones, and run without them, with the exception of the first run after restarting my restify service server, usually only the first or second Then/And fails, the rest succeed. There is a timing issue of maybe a few milliseconds, but definitely not 2000ms. I'm curious why my Givens and Whens suddenly start timing out when I throw in the done() calls.
I am pretty sure I am misunderstanding how mocha-cakes is transforming the coffescript Feature->Scenario->Given/When->Then/And/... into describe/it calls. I suspect that somehow the scope within which done applies is larger than it would seem to be given the nature of mocha-cakes script structure...I'm just not sure exactly what that scope is.
I am also not familiar with mocha-cakes. I am using mocha/(lit)coffee to test restify. I have found it convenient to wrap my calls in promises, as latest mocha is promise-aware. Then I don't have to bother with "done". Also note you may need to call res.end() or res.resume() (See this explanation)
For just "GET":
Promise = require('bluebird') # or whatever, I presume
port = 8776 # ditto
getHttpJson = (addr)->
addr = normalizeUrl(addr)
new Promise ( resolve, reject )->
req = http.get(addr, _completeResponse(resolve) )
.on( 'error', reject )
req.end()
General case:
requestHttpJson = (method, addr, data)->
if data?
data = JSON.stringify(data)
urlBits = getUrlBits(addr)
new Promise (resolve, reject)->
req = http.request(
method: method
headers: {
"Content-Type": "application/json" }
hostname: urlBits.hostname
port: urlBits.port
path: urlBits.pathname
, _completeResponse(resolve) )
req.on( 'error', reject )
if data?
req.write( data )
req.end()
postHttpJson = (addr, data)->
requestHttpJson('POST', addr, data)
putHttpJson = (addr, data)->
requestHttpJson('PUT', addr, data)
deleteHttpJson = (addr, data)->
requestHttpJson('DELETE', addr, data)
Break down address into components and add defaults. ("port" is a module global.)
getUrlBits = (addr)->
bits = url.parse(addr)
bits.port = bits.port || port
bits.hostname = bits.hostname || 'localhost'
bits.protocol = bits.protocol || 'http'
return bits
normalizeUrl = (addr)->
url.format(getUrlBits(addr))
Utility to parse body of request & resolve.
_completeResponse = (resolve)->
(res)->
body = []
res.on 'data', (data)->
body.push data
res.on 'end', ->
body = body.join ''
content = if body == '' then null else JSON.parse(body)
resolve([res,content])