I'm currently building a Google Cloud Endpoints backend with the endpoints-proto-datastore library, and am running into trouble requiring an apikey when you request a user.
Once a user logs in, they receive an APIkey which they send back for successive puts (which works) but how do I require the username/email and apikey on a GET ? Currently if a user does a get like so:
#User.method(request_fields=('id', 'apiToken',), path='users', http_method='GET', name='user.get')
def user_get(self, query):
return query
The user is pulled from the datastore because the ID is correct, and it completely ignores the apiToken. How do I require both fields?
(on a different note, how do I send back the user's ID on a request?)
If you are implementing your own API key scheme, as your code suggests, then you need to manually check if the API key is valid or not yourself.
Your example looks like the one from the 'basic' example, and you've added parameters as per the 'simple_get' example. For some background, the docs in the 'simple_get' example mention that 'id' is one of five special helper properties automatically defined by EndpointsModel for common operations like retrieving by id. This is why your code works automatically without you doing anything 'special' with the 'id' parameter. The example still checks though that the entity exists if you try to get it:
if not my_model.from_datastore:
raise endpoints.NotFoundException('MyModel not found.')
Since there's no special helper property for your 'apiKey' field, you need to add your own code in the method to check if the key is valid and return a 401 or suitable error if it's not. Another option is to also utilize some of Google's built-in authentication as per the 'basic_with_auth' example.
Finally, since endpoints-proto-datastore is just syntactic sugar for the main endpoints library, you'll want to read the full documentation on that for more information on things like how to return values from your endpoints methods.
The easiest way I found to do this is:
#User.method(request_fields=('id', 'apiToken',), path='users', http_method='GET', name='user.get')
def user_get(self, user_model):
user = ndb.Key('User', int(user_model.id)).get()
if user.apiToken != user_model.apiToken:
raise endpoints.UnauthorizedException('You are not authorized to view this data')
return user.clean()
The user_model will have the userId and the apiToken stored in it, so I pull the "real" data from ndb with the key and check if the user_model has the correct token and return the model if it is correct, if not, I refuse
Related
I am using Django and DRF for APIs. Suppose I have an object with three fields (name, password, email). When a user wants to update his name, password and email at the same time, is it okay for me to send a PATCH request instead of a PUT request? What are the downfalls?
I feel this is more convenient than checking if the user is updating all fields, and if he is, using a PUT request, but if he's not, using a PATCH.
Checking if the object exists is already taken care of in the backend, and when creating objects, I use CREATE / POST instead of PUT because it is more convenient for me.
Edit: I am using AngularJS's PATCH: https://docs.angularjs.org/api/ng/service/$http#patch to send content to the backend, which is handled by DRF Viewset (which handles PATCH requests).
It's OK to use PATCH. However, note that it will not check for required fields as opposed to PUT.
It's not OK to use PATCH unless you use a media type that has defined semantics for PATCH. "application/json" does not.
See https://www.rfc-editor.org/errata_search.php?eid=3169:
If the operation does not modify the resource identified by the
Request-URI in a predictable way that's defined by the semantics
of the PATCH media type, POST should be considered instead of
PATCH or PUT.
FWIW, see RFC 6902 and RFC 7386 for types you could use.
In my application , I am going to use connectwise API , but I can't figure out how to call their API, like
How to connect
How to pass header (json data)
How to authentication (I have company id , public and private key)
How to make call and take response
I am Python guy
Thanks in advance
This question is quite broad, and you'll need to get familiar with something such as the Requests module if you aren't already.
Also, ConnectWise has a lot of documents at their developer site and registration is free and easy.
However I went through this journey myself over the last few weeks and learnt a lot so I'll share it with you.
Authentication
Authentication is done by creating an API user. In CW Manage you can create a member user with the API license class.
In ConnectWise Manage:
Go to System => Members
Go to the API Members tab.
Create a new API member that gives the API access to the areas that you need it, such as corporate/territorial levels. Note the username, amd that you cannot specify a password. Complete all the mandatory fields (owner ID, system default, group, approvers, etc).
When you have made this user go to the API keys tab. Enter a description for your new set of keys, and save it. When you do this you'll see the public and private keys once (and once only). Note them down.
Authorisation Header
In Python:
>>> import base64
>>> base64.b64encode("a+b:c")
...where a is your ConnectWise company name (what you type in in the login box), b is your public key, and c is your private key. This will return something which will be what you use to access the system. Copy it.
Making your first request
Now in Python make a new file and put this in it (this is quick and dirty but it should get you started):
import requests
cwToken = ""
# This is the ConnectWise access code generated earlier
cwUrl = "https://api-eu.myconnectwise.net/v4_6_release/apis/3.0/"
# check the URL matches your region, look at your CW Manage login box if not
cwHeaders = {"Authorization":"Basic " + cwToken,
"clientID":"<insertyoursecretClientIDhere>",
"Content-Type":"application/json"}
# this is your authorisation payload
try:
r = requests.get(cwUrl + "company/companies?pageSize=1000&conditions=type/id==1", headers=cwHeaders)
# request has been made
r.raise_for_status()
except:
print(r.text)
raise
companies = r.json()
The companies object now contains a list of your first 1,000 clients (type is ID 1 -- client). I've included an example of the conditions string because it took me a while to work out what it is and how to use it. But just take it off if you don't want the server to do your filtering for you.
You'll then be able to modify the above, or turn it into a module (which is what I did) to then make quick and portable calls, something like
cw.getCompanies()
which would simply return you a JSON object with all your companies in.
Going further
Study the documentation!! It is comprehensive enough to get you started. And the forums are actively monitored by staff (although they help mostly with C# queries and not scripting).
Just an update for 2020 since Daniel's response was incredibly helpful, but is missing a change Connectwise made in 2019 that now requires ClientIDs in order to auth.
Make a client ID here (scroll to bottom, although it's quick and useful to read all of it):
https://developer.connectwise.com/ClientID#What_is_a_clientId.3F
Daniel's code should then look like:
cwHeaders = {"Authorization":"Basic " + cwToken,
"clientID":"<insertyoursecretClientIDhere>",
"Content-Type":"application/json"}
Is it possible to get the current User’s roles accessible in a remote method in Loopback?
I’m trying to allow a remote method to either return a subset of data based on a find using a where filter & current userId but in the case of an admin user, I simply want to return a full set of data.
So I have been trying to obtain the list of roles for the current user. But I am struggling to make Role.getRoles() return anything other than:
[ '$unauthenticated', '$everyone' ]
I have tried the context from loopback.getCurrentContext() and the context passed into the beforeRemote method and I have tried ACL.checkAccessForContext().
Any help would be appreciated.
This can happen for many reasons, one is that the Role model is not related to your User model (see here for brief explanation). Can you check that?
Other than that, it's probably that first argument (context) which we need to have better documentation on ( sorry :( ). If you look at the tests for the role class you can see how it might be used.
var RoleMapping = loopback.RoleMapping;
RoleMapping.attachTo(ds); // `ds` is your data source
// ...
Role.getRoles({principalType: RoleMapping.USER, principalId: user.id}, function(err, roles) {
console.log(roles); // everyone, authenticated, etc (hopefully)
});
What I'm trying to do is whenever the user requests an API key--regardless of whether the user already generated one or not--the system will generate an entirely new key.
I know that whenever calling ApiKey.objects.create() will generate an API key for the user that doesn't have one generated. However, if a user does have one, then trying to call the .create() method throws an error.
In this case, I figured that it would be best to write my own key generator. However, I am now hoping that maybe someone here might know of a helper function that will allow me to generate a random API key, and then let me save it to the database manually myself.
Would anyone might know of any such a helper function?
Or, you can just use tastypie's built-in command:
python manage.py backfill_api_keys
I figured it out.
First, you make an attempt to get the the user's API key. If it exists, then there will be no error thrown. To regenerate, set the value of the retrieved user's key to None, and then save the key.
If there was an error thrown, then simply create a new key.
try:
api_key = ApiKey.objects.get(user=someuser)
api_key.key = None
api_key.save()
except ApiKey.DoesNotExist:
api_key = ApiKey.objects.create(user=someuser)
Yes, the code for generating the key is defined as an instance method ApiKey.generate_key() which you can use directly.
Here's a simpler version that takes out some of the guesswork of whether the user already exists or not and uses ApiKey.generate_key() directly, rather than implicitly through ApiKey.save(), which I believe makes it a bit more clearer of what's trying to be accomplished:
api_key = ApiKey.objects.get_or_create(user=someuser)
api_key.key = api_key.generate_key()
api_key.save()
UPDATE:
Thus, the shortest version is:
return ApiKey.objects.get_or_create(user=someuser)[0].key
This will generate a new key or return an existing one.
Based on Filip Dupanović's answer the working code for me was something like this:
user = get_user_model().objects.get(email="some#email.com")
api_key = ApiKey.objects.get_or_create(user=user)
api_key[0].key = api_key[0].generate_key()
api_key[0].save()
Its way too easy to use inbuilt functions, always. To generate Api keys in Tastypie use "create_api_key" of TastypiesApikeyAuthentication`.
you have to just import "create_api_key" from tastypie.models
and then call it by django-signal or as per u require.
i.e.
signals.post_save.connect(create_api_key, sender=User)
Explained in detailed and easier at :
http://django-tastypie.readthedocs.org/en/latest/authentication.html#apikeyauthentication
I am writing forum app on Django using custom session/auth/users/acl system. One of goals is allowing users to browse and use my app even if they have cookies off. Coming from PHP world, best solution for problem is appending sid= to every link on page. Here is how I plan to do it:
Session middleware checks if user has session cookie or remember me cookie. If he does, this most likely means cookies work for him. If he doesnt, we generate new session ID, open new session (make new entry in sessions table in DB), then send cookie and redirect user to where he is, but with SID appended to url. After redirect middleware will see if session id can be obtained from either cookie or GET. If its cookie, we stop adding sid to urls. If its GET, we keep them.
I plan to insert SID= part into url's by decorating django.core.urlresolvers.reverse and reverse_lazy with my own function that appends ?sid= to them. However this raises some problems because both middlewares urlresolvers and are not thread safe. To overcome this I created something like this:
class SessionMiddleware(object):
using_decorator = False
original_reverse = None
def process_request(self, request):
self.using_decorator = True
self.original_reverse = urlresolvers.reverse
urlresolvers.reverse = session_url_decorator(urlresolvers.reverse, 's87add8ash7d6asdgas7dasdfsadas')
def process_response(self, request, response):
# Turn off decorator if we are using it
if self.using_decorator:
urlresolvers.reverse = self.original_reverse
self.using_decorator = False
return response
If SID has to be passed via links, process_request sets using_decorator to true and stores undecorated urlresolvers.revers in separate method. After page is rendered process_response checks using_decorator to see if it has to perform "garbage collection". If it does, it returns reverse function to original undecorated state.
My question is, is this approach thread-safe? Or will increase in traffic on my forum may result in middleware decorating those functions again and again and again, failing to run "garbage collection"? I also tought about using regex to simply skim generated HTML response for links and providing template filters and variables for manually adding SID to places that are omitted by regex.
Which approach is better? Also is current one thread safe?
First of all: Using SIDs in the URL is quite dangerous, eg if you copy&paste a link for a friend he is signed in as you. Since most users don't know what a SID is they will run into this issue. As such you should never ever use SIDs in the url and since Facebook and friends all require cookies you should be fine too...
Considering that, monkeypatching urlresolvers.reverse luckily doesn't work! Might be doable with a custom URLResolvers subclass, but I recommend against it.
And yes, your middleware is not threadsafe. Middlewares are initialized only once and shared between threads, meaning that storing anything on self is not threadsafe.