CORD-844 add session token authentication
Change-Id: Ic43c8794c5bfdd2c82cae1ec717826a84c34a212
diff --git a/xos/grpc/apihelper.py b/xos/grpc/apihelper.py
index bf128c2..b757ae7 100644
--- a/xos/grpc/apihelper.py
+++ b/xos/grpc/apihelper.py
@@ -7,6 +7,10 @@
from core.models import *
from xos.exceptions import *
+from importlib import import_module
+from django.conf import settings
+SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
+
class XOSAPIHelperMixin(object):
def getProtoClass(self, djangoClass):
pClass = getattr(xos_pb2, djangoClass.__name__)
@@ -146,9 +150,18 @@
(username, password) = auth.split(":")
user = django_authenticate(username=username, password=password)
if not user:
- raise Exception("failed to authenticate %s:%s" % (username, password))
+ raise XOSPermissionDenied("failed to authenticate %s:%s" % (username, password))
print "authenticated %s:%s as %s" % (username, password, user)
return user
+ elif (k.lower()=="x-xossession"):
+ s = SessionStore(session_key=v)
+ id = s.get("_auth_user_id", None)
+ if not id:
+ raise XOSPermissionDenied("failed to authenticate token %s" % v)
+ user = User.objects.get(id=id)
+ print "authenticated sessionid %s as %s" % (v, user)
+ return user
+
return None
diff --git a/xos/grpc/grpc_client.py b/xos/grpc/grpc_client.py
index 8defd42..cd561c7 100644
--- a/xos/grpc/grpc_client.py
+++ b/xos/grpc/grpc_client.py
@@ -2,7 +2,8 @@
import grpc
from protos.common_pb2 import *
from protos.xos_pb2 import *
-from protos import xos_pb2_grpc, modeldefs_pb2_grpc
+from protos.utility_pb2 import *
+from protos import xos_pb2_grpc, modeldefs_pb2_grpc, utility_pb2_grpc
from google.protobuf.empty_pb2 import Empty
from grpc import metadata_call_credentials, ChannelCredentials, composite_channel_credentials, ssl_channel_credentials
@@ -18,6 +19,14 @@
metadata = (('Authorization', basic_auth),)
callback(metadata, None)
+class SessionIdCallCredentials(grpc.AuthMetadataPlugin):
+ """Metadata wrapper for raw access token credentials."""
+ def __init__(self, sessionid):
+ self._sessionid = sessionid
+ def __call__(self, context, callback):
+ metadata = (('x-xossession', self._sessionid),)
+ callback(metadata, None)
+
class XOSClient(object):
def __init__(self, hostname, port):
self.hostname = hostname
@@ -29,19 +38,24 @@
self.channel = grpc.insecure_channel("%s:%d" % (self.hostname, self.port))
self.stub = xos_pb2_grpc.xosStub(self.channel)
self.modeldefs = modeldefs_pb2_grpc.modeldefsStub(self.channel)
+ self.utility = utility_pb2_grpc.utilityStub(self.channel)
class SecureClient(XOSClient):
- def __init__(self, hostname, port=50051, cacert=SERVER_CA, username=None, password=None):
+ def __init__(self, hostname, port=50051, cacert=SERVER_CA, username=None, password=None, sessionid=None):
super(SecureClient,self).__init__(hostname, port)
server_ca = open(cacert,"r").read()
- call_creds = metadata_call_credentials(UsernamePasswordCallCredentials(username, password))
+ if (sessionid):
+ call_creds = metadata_call_credentials(SessionIdCallCredentials(sessionid))
+ else:
+ call_creds = metadata_call_credentials(UsernamePasswordCallCredentials(username, password))
chan_creds = ssl_channel_credentials(server_ca)
chan_creds = composite_channel_credentials(chan_creds, call_creds)
self.channel = grpc.secure_channel("%s:%d" % (self.hostname, self.port), chan_creds)
self.stub = xos_pb2_grpc.xosStub(self.channel)
self.modeldefs = modeldefs_pb2_grpc.modeldefsStub(self.channel)
+ self.utility = utility_pb2_grpc.utilityStub(self.channel)
def main(): # self-test
client = InsecureClient("xos-core.cord.lab")
diff --git a/xos/grpc/grpc_server.py b/xos/grpc/grpc_server.py
index d231bfc..8ffa8c3 100644
--- a/xos/grpc/grpc_server.py
+++ b/xos/grpc/grpc_server.py
@@ -29,9 +29,10 @@
sys.path.append('/opt/xos')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
-from protos import xos_pb2, schema_pb2, modeldefs_pb2
+from protos import xos_pb2, schema_pb2, modeldefs_pb2, utility_pb2
from xos_grpc_api import XosService
from xos_modeldefs_api import ModelDefsService
+from xos_utility_api import UtilityService
from google.protobuf.empty_pb2 import Empty
from xos.logger import Logger, logging
@@ -118,6 +119,7 @@
(schema_pb2.add_SchemaServiceServicer_to_server, SchemaService),
(xos_pb2.add_xosServicer_to_server, XosService),
(modeldefs_pb2.add_modeldefsServicer_to_server, ModelDefsService),
+ (utility_pb2.add_utilityServicer_to_server, UtilityService),
):
service = service_class(self.thread_pool)
self.register(activator_func, service)
diff --git a/xos/grpc/protos/utility.proto b/xos/grpc/protos/utility.proto
new file mode 100644
index 0000000..36b5ed0
--- /dev/null
+++ b/xos/grpc/protos/utility.proto
@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+package xos;
+
+import "google/protobuf/empty.proto";
+import "google/api/annotations.proto";
+import "common.proto";
+
+message LoginRequest {
+ string username = 1;
+ string password = 2;
+};
+
+message LoginResponse {
+ string sessionid = 1;
+};
+
+service utility {
+
+ rpc Login(LoginRequest) returns (LoginResponse) {
+ option (google.api.http) = {
+ post: "/xosapi/v1/utility/login"
+ body: "*"
+ };
+ }
+
+ rpc Logout(google.protobuf.Empty) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ post: "/xosapi/v1/utility/logout"
+ body: "*"
+ };
+ }
+};
diff --git a/xos/grpc/xos_utility_api.py b/xos/grpc/xos_utility_api.py
new file mode 100644
index 0000000..eb580ac
--- /dev/null
+++ b/xos/grpc/xos_utility_api.py
@@ -0,0 +1,51 @@
+import base64
+import time
+from protos import utility_pb2
+from google.protobuf.empty_pb2 import Empty
+
+from importlib import import_module
+from django.conf import settings
+SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
+
+from django.contrib.auth import authenticate as django_authenticate
+import django.apps
+from core.models import *
+from xos.exceptions import *
+from apihelper import XOSAPIHelperMixin
+
+class UtilityService(utility_pb2.utilityServicer, XOSAPIHelperMixin):
+ def __init__(self, thread_pool):
+ self.thread_pool = thread_pool
+
+ def stop(self):
+ pass
+
+ def Login(self, request, context):
+ if not request.username:
+ raise XOSPermissionDenied("No username")
+
+ u=django_authenticate(username=request.username, password=request.password)
+ if not u:
+ raise XOSPermissionDenied("Failed to authenticate user %s" % request.username)
+
+ session = SessionStore()
+ auth = {"username": request.username, "password": request.password}
+ session["auth"] = auth
+ session['_auth_user_id'] = u.pk
+ session['_auth_user_backend'] = u.backend
+ session.save()
+
+ response = utility_pb2.LoginResponse()
+ response.sessionid = session.session_key
+
+ return response
+
+ def Logout(self, request, context):
+ for (k, v) in context.invocation_metadata():
+ if (k.lower()=="x-xossession"):
+ s = SessionStore(session_key=v)
+ if "_auth_user_id" in s:
+ del s["_auth_user_id"]
+ s.save()
+ return Empty()
+
diff --git a/xos/tools/apigen/grpc_list_test.template.py b/xos/tools/apigen/grpc_list_test.template.py
index fd22503..9b7fffb 100644
--- a/xos/tools/apigen/grpc_list_test.template.py
+++ b/xos/tools/apigen/grpc_list_test.template.py
@@ -12,7 +12,21 @@
c=grpc_client.SecureClient("xos-core.cord.lab", username="padmin@vicci.org", password="letmein")
{% for object in generator.all() %}
-print "testing secure List{{ object.camel() }}...",
+print "testing basic secure List{{ object.camel() }}...",
+c.stub.List{{ object.camel() }}(Empty())
+print "Okay"
+{%- endfor %}
+
+# now try to login
+c=grpc_client.InsecureClient("xos-core.cord.lab")
+lr=grpc_client.LoginRequest()
+lr.username="padmin@vicci.org"
+lr.password="letmein"
+session=c.utility.Login(lr)
+
+c=grpc_client.SecureClient("xos-core.cord.lab", sessionid=session.sessionid)
+{% for object in generator.all() %}
+print "testing session secure List{{ object.camel() }}...",
c.stub.List{{ object.camel() }}(Empty())
print "Okay"
{%- endfor %}