Implement Google OAuth2 user validation and upload logo functionality

1 parent fe13e5ef
...@@ -5,14 +5,18 @@ import importlib ...@@ -5,14 +5,18 @@ import importlib
import csv import csv
import re import re
import datetime import datetime
import deform
import decimal import decimal
import xlsx_dict_reader
from openpyxl import load_workbook
from opensipkd.tools import get_settings, DefaultTimeZone, dmy, dmyhms, get_ext
from pkg_resources import resource_filename
from pyramid.renderers import JSON from pyramid.renderers import JSON
from pyramid_beaker import session_factory_from_settings from pyramid_beaker import session_factory_from_settings
from pyramid.config import Configurator from pyramid.config import Configurator
from pyramid.events import NewRequest, BeforeRender, subscriber from pyramid.events import NewRequest, BeforeRender, subscriber
from pyramid_mailer import mailer_factory_from_settings from pyramid_mailer import mailer_factory_from_settings
from opensipkd.tools import get_settings, DefaultTimeZone, dmy, dmyhms
from .security import MySecurityPolicy, get_user from .security import MySecurityPolicy, get_user
from sqlalchemy import engine_from_config from sqlalchemy import engine_from_config
from .models.base import DBSession from .models.base import DBSession
...@@ -20,11 +24,9 @@ from .models.handlers import LogDBSession ...@@ -20,11 +24,9 @@ from .models.handlers import LogDBSession
from .models.meta import Base from .models.meta import Base
from .models.users import init_model from .models.users import init_model
from pkg_resources import resource_filename
# from deform import ZPTRendererFactory, Form # from deform import ZPTRendererFactory, Form
# from deform.widget import default_resource_registry # from deform.widget import default_resource_registry
import deform
_logging = logging.getLogger(__name__) _logging = logging.getLogger(__name__)
# version 2.0.0 digunakan untuk change default template # version 2.0.0 digunakan untuk change default template
...@@ -320,7 +322,7 @@ def main(global_config, **settings): ...@@ -320,7 +322,7 @@ def main(global_config, **settings):
init_db(settings=settings) init_db(settings=settings)
config = get_config(settings=settings) config = get_config(settings=settings)
BASE_CLASS.route_from_csv(config) BASE_CLASS.route_from_csv(config, filename="routes.xlsx")
BASE_CLASS.route_from_list(config) BASE_CLASS.route_from_list(config)
BASE_CLASS.static_view(config, settings=settings) BASE_CLASS.static_view(config, settings=settings)
config.scan() config.scan()
...@@ -497,24 +499,57 @@ class BaseApp(): ...@@ -497,24 +499,57 @@ class BaseApp():
if p["children"]: if p["children"]:
self.route_children(p["children"], row) self.route_children(p["children"], row)
def route_from_csv(self, config, paket="opensipkd.base.views", filename="routes.csv"):
with self.get_route_file(filename) as f:
rows = csv.DictReader(f)
new_routes = []
for row in rows:
status = row.get("status", 0)
if not row["kode"] or not int(status):
continue
status = int(status)
row["children"] = []
parent_id = row.get("parent_id") or row.get("parent_id/routes.kode")
if parent_id:
self.route_children(new_routes, row)
else:
new_routes.append(row)
self.add_menu(config, new_routes, None, paket) def route_from_csv_(self, config, paket="tangsel.base.views", rows=[]):
new_routes = []
for row in rows:
status = row.get("status", 0)
if not row["kode"] or not int(status):
continue
status = int(status)
row["children"] = []
parent_id = row.get("parent_id") or row.get(
"parent_id/routes.kode")
if parent_id:
self.route_children(new_routes, row)
else:
new_routes.append(row)
self.add_menu(config, new_routes, None, paket)
def route_from_csv(self, config, paket="opensipkd.base.views", filename="routes.csv"):
fullpath = os.path.join(self.base_dir, 'scripts', 'data', filename)
if get_ext(filename) == ".csv":
with open(fullpath) as f:
rows = csv.DictReader(f, skipinitialspace=True)
self.route_from_csv_(config, paket, rows=rows)
else:
wb= load_workbook(fullpath, data_only=True)
ws = wb.active
rows = xlsx_dict_reader.DictReader(ws) #skip_blank=True
self.route_from_csv_(config, paket, rows=rows)
# with self.get_route_file(filename) as f:
# rows = csv.DictReader(f)
# new_routes = []
# for row in rows:
# status = row.get("status", 0)
# if not row["kode"] or not int(status):
# continue
# status = int(status)
# row["children"] = []
# parent_id = row.get("parent_id") or row.get("parent_id/routes.kode")
# if parent_id:
# self.route_children(new_routes, row)
# else:
# new_routes.append(row)
# self.add_menu(config, new_routes, None, paket)
def route_from_list(self, config, routs=[], paket="opensipkd.base.views"): def route_from_list(self, config, routs=[], paket="opensipkd.base.views"):
new_routes = [] new_routes = []
......
import logging
from google.auth.transport import requests
from google.oauth2 import id_token
from opensipkd.base import get_params
from pyramid.view import (view_config, )
from opensipkd.models import User
from opensipkd.tools import get_settings
import json
_logging = logging.getLogger(__name__)
def validate_user(request, idinfo):
"""
Digunakan untuk memvalidasi token google dalam password
Langkah yang dilakukan query email dan simpan sebagai session
:param idinfo:
:return:
{
// These six fields are included in all Google ID Tokens.
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
"aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
"iat": "1433978353",
"exp": "1433981953",
// These seven fields are only included when the user has granted the "profile" and
// "email" OAuth scopes to the application.
"email": "testuser@gmail.com",
"email_verified": "true",
"name" : "Test User",
"picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
"given_name": "Test",
"family_name": "User",
"locale": "en"
}
"""
email = 'email' in idinfo and idinfo['email']
if not email:
raise ValueError('Mail tidak ditemukan')
user = User.get_by_identity(email)
return user
@view_config(route_name='googleOauth2', renderer='json')
def google_oauth2(request):
return dict()
@view_config(route_name='googlesignin', renderer='json')
def googlesignin(request, data=None):
# (Receive token by HTTPS POST)
# ...
CLIENT_IDS = request.google_signin_client_ids
# CLIENT_IDS = get_params('google-signin-client-id')
KEY = get_params('google-signin-client-secret')
# Specify the CLIENT_ID of the app that accesses the backend:
# idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
# Or, if multiple clients access the backend server:
id_token = "id_token" in request.params and request.params[
'id_token'] or ""
gtoken = None
if id_token:
gtoken = json.loads(id_token)
else:
if data and "id_token" in data:
gtoken = data["id_token"]
_logging.debug(gtoken)
if not gtoken:
raise Exception("Gtoken not found")
# idinfo = id_token.verify_oauth2_token(gtoken, requests.Request())
# test
import jwt
idinfo = jwt.decode(gtoken["credential"], options={
"verify_signature": False}) # KEY, algorithms=["RS256"]) #
_logging.debug(CLIENT_IDS)
_logging.debug(idinfo)
if idinfo['aud'] not in CLIENT_IDS or idinfo['azp'] not in CLIENT_IDS:
raise ValueError('Could not verify audience.')
if idinfo['iss'] not in ['accounts.google.com',
'https://accounts.google.com']:
raise ValueError('Wrong issuer.')
return idinfo
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" tal:define="home request.home;"> <html lang="en" tal:define="home request.home;">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
...@@ -10,7 +11,8 @@ ...@@ -10,7 +11,8 @@
<title tal:content="request.title" /> <title tal:content="request.title" />
<!-- SmartAdmin Styles : Caution! DO NOT change the order --> <!-- SmartAdmin Styles : Caution! DO NOT change the order -->
<link rel="stylesheet" type="text/css" media="screen" href="${home}/static/v3/css/smartadmin-production-plugins.min.css"> <link rel="stylesheet" type="text/css" media="screen"
href="${home}/static/v3/css/smartadmin-production-plugins.min.css">
<link rel="stylesheet" type="text/css" media="screen" href="${home}/static/v3/css/smartadmin-production.min.css"> <link rel="stylesheet" type="text/css" media="screen" href="${home}/static/v3/css/smartadmin-production.min.css">
<link rel="stylesheet" type="text/css" media="screen" href="${home}/static/v3/css/smartadmin-skins.min.css"> <link rel="stylesheet" type="text/css" media="screen" href="${home}/static/v3/css/smartadmin-skins.min.css">
...@@ -24,8 +26,8 @@ ...@@ -24,8 +26,8 @@
<link href="${home}/static/v3/plugin/jqueryui/themes/base/jquery-ui.min.css" rel="stylesheet"> <link href="${home}/static/v3/plugin/jqueryui/themes/base/jquery-ui.min.css" rel="stylesheet">
<!-- DataTables --> <!-- DataTables -->
<!-- <link href="${home}/static/v3/plugin/datatables/1.10/media/css/dataTables.bootstrap.css" rel="stylesheet"> --> <!-- <link href="${home}/static/v3/plugin/datatables/1.10/media/css/dataTables.bootstrap.css" rel="stylesheet"> -->
<!-- <link href="${home}/deform_static/css/typeahead.css" rel="stylesheet"> --> <!-- <link href="${home}/deform_static/css/typeahead.css" rel="stylesheet"> -->
<link href="${home}/deform_static/css/form.css" rel="stylesheet"> <link href="${home}/deform_static/css/form.css" rel="stylesheet">
<link href="${home}/static/css/theme.css" rel="stylesheet"> <link href="${home}/static/css/theme.css" rel="stylesheet">
<link href="${home}/static/css/navbar-fixed-top.css" rel="stylesheet"> <link href="${home}/static/css/navbar-fixed-top.css" rel="stylesheet">
...@@ -34,7 +36,7 @@ ...@@ -34,7 +36,7 @@
<metal:css define-slot="css_files"></metal:css> <metal:css define-slot="css_files"></metal:css>
<style> <style>
#content { #content {
padding-top: 0px; padding-top: 0px;
} }
</style> </style>
</head> </head>
...@@ -57,15 +59,15 @@ ...@@ -57,15 +59,15 @@
</div> </div>
<!-- <div class="navbar-collapse collapse" tal.condition="not request.user"> --> <!-- <div class="navbar-collapse collapse" tal.condition="not request.user"> -->
<!-- <ul class="nav navbar-nav"> --> <!-- <ul class="nav navbar-nav"> -->
<!-- <li><a href="${home}/eis/sipkd" class="navbar-brand txt-color-white"><strong>${request.company}</strong></a></li> --> <!-- <li><a href="${home}/eis/sipkd" class="navbar-brand txt-color-white"><strong>${request.company}</strong></a></li> -->
<!-- </ul> --> <!-- </ul> -->
<!-- <ul class="nav navbar-nav navbar-right" style="margin-right:0px;"> --> <!-- <ul class="nav navbar-nav navbar-right" style="margin-right:0px;"> -->
<!-- <li class="dropdown"> --> <!-- <li class="dropdown"> -->
<!-- <a href="${home}/login" class="button txt-color-white"><i class="fa fa-user"></i> Masuk</a> --> <!-- <a href="${home}/login" class="button txt-color-white"><i class="fa fa-user"></i> Masuk</a> -->
<!-- </li> --> <!-- </li> -->
<!-- </ul> --> <!-- </ul> -->
<!-- </div> --> <!-- </div> -->
<div class="navbar-collapse collapse" tal:condition="request.user"> <div class="navbar-collapse collapse" tal:condition="request.user">
...@@ -74,43 +76,47 @@ ...@@ -74,43 +76,47 @@
<!-- End Tombol Navigator --> <!-- End Tombol Navigator -->
<ul class="nav navbar-nav navbar-right" style="margin-right:0px;"> <ul class="nav navbar-nav navbar-right" style="margin-right:0px;">
<!-- Admin Menu --> <!-- Admin Menu -->
<li <li class="dropdown" tal:condition="has_permission(request, ['user-view', 'user-edit'])"
class="dropdown" tal:attributes="class request.matched_route.name in ['user', 'user-add', 'user-edit', 'user-delete', 'group', 'group-add', 'group-edit', 'group-delete'] and 'active'">
tal:condition="has_permission(request, ['user-view', 'user-edit'])"
tal:attributes="class request.matched_route.name in ['user', 'user-add', 'user-edit', 'user-delete', 'group', 'group-add', 'group-edit', 'group-delete'] and 'active'">
<a href="#" class="dropdown-toggle txt-color-white" data-toggle="dropdown">Admin <b class="caret"></b></a> <a href="#" class="dropdown-toggle txt-color-white" data-toggle="dropdown">Admin <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li tal:condition="has_permission(request, ['user-view', 'user-edit'])">
<a href="${home}/user">User</a>
</li>
<li tal:condition="has_permission(request, ['user-view', 'user-edit'])"> <li tal:condition="has_permission(request, ['user-view', 'user-edit'])">
<a href="${home}/group">Group</a> <a href="${home}/user">User</a>
</li>
<li tal:condition="has_permission(request, ['user-view', 'user-edit'])">
<a href="${home}/group">Group</a>
</li>
<!-- <li tal:condition="has_permission(request, 'routes')"><a href="${home}/routes">Routes</a></li> -->
<li tal:condition="has_permission(request, 'upload-logo')"><a href="${home}/upload/logo">Upload Logo</a>
</li> </li>
<li tal:condition="has_permission(request, 'routes')"><a href="${home}/routes">Routes</a></li>
<li tal:condition="has_permission(request, 'upload-logo')"><a href="${home}/upload/logo">Upload Logo</a></li>
<li tal:condition="has_permission(request, 'parameter')"> <li tal:condition="has_permission(request, 'parameter')">
<a href="${home}/parameter">Parameter</a></li> <a href="${home}/parameter">Parameter</a>
<li tal:condition="has_permission(request, 'departemen')"> </li>
<a href="${home}/departemen">Departemen</a></li> <!-- <li tal:condition="has_permission(request, 'departemen')">
<a href="${home}/departemen">Departemen</a></li> -->
<li tal:condition="has_permission(request, 'partner')"> <li tal:condition="has_permission(request, 'partner')">
<a href="${home}/partner">Partner</a></li> <a href="${home}/partner">Partner</a>
<li tal:condition="has_permission(request, 'parameter')"> </li>
<a href="${home}/parameter">Parameter</a></li> <!-- <li tal:condition="has_permission(request, 'parameter')">
<a href="${home}/parameter">Parameter</a>
</li> -->
</ul> </ul>
</li> </li>
<!-- User Login Menu--> <!-- User Login Menu-->
<li class="dropdown" tal:attributes="class request.path in <li class="dropdown" tal:attributes="class request.path in
['/password', '/recreate-api-key'] and 'active'"> ['/password', '/recreate-api-key'] and 'active'">
<a href="#" class="dropdown-toggle txt-color-white" data-toggle="dropdown">My Account <b class="caret"></b></a> <a href="#" class="dropdown-toggle txt-color-white" data-toggle="dropdown">My Account <b
class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a style="text-transform:capitalize" href="${home}/logout">${request.user.nice_username()} Logout</a></li> <li><a style="text-transform:capitalize" href="${home}/logout">${request.user.nice_username()} Logout</a>
</li>
<li><a style="text-transform:capitalize" href="${home}/profile">Profile</a></li> <li><a style="text-transform:capitalize" href="${home}/profile">Profile</a></li>
<li><a style="text-transform:capitalize" href="${home}/password">Ubah password</a></li> <li><a style="text-transform:capitalize" href="${home}/password">Ubah password</a></li>
<li tal:condition="request.user.api_key"> <li tal:condition="request.user.api_key">
<a style="text-transform:capitalize" href="${home}/recreate-api-key"> <a style="text-transform:capitalize" href="${home}/recreate-api-key">
API Key API Key
</a> </a>
</li> </li>
<!-- <li talcondition="'core' in request.modules and change_unit(request)"><a style="text-transform:capitalize" href="${home}/departemen/chg">Ubah Organisasi</a></li> --> <!-- <li talcondition="'core' in request.modules and change_unit(request)"><a style="text-transform:capitalize" href="${home}/departemen/chg">Ubah Organisasi</a></li> -->
</ul> </ul>
...@@ -120,42 +126,45 @@ ...@@ -120,42 +126,45 @@
</div> </div>
</div> </div>
<div class="container-fluid"> <div class="container-fluid">
<!-- Error session flash --> <!-- Error session flash -->
<div tal:condition="request.session.peek_flash()"> <div tal:condition="request.session.peek_flash()">
<div class="alert alert-success" tal:repeat="message request.session.pop_flash()"><i class="fa fa-fw fa-lg fa-check-circle"></i>&nbsp;${message}</div> <div class="alert alert-success" tal:repeat="message request.session.pop_flash()"><i
</div> class="fa fa-fw fa-lg fa-check-circle"></i>&nbsp;${message}</div>
<div tal:condition="request.session.peek_flash('error')"> </div>
<div class="alert alert-danger" tal:repeat="message request.session.pop_flash('error')"><i class="fa fa-fw fa-lg fa-times-circle"></i>&nbsp;${message}</div> <div tal:condition="request.session.peek_flash('error')">
</div> <div class="alert alert-danger" tal:repeat="message request.session.pop_flash('error')"><i
<!-- Error session flash --> class="fa fa-fw fa-lg fa-times-circle"></i>&nbsp;${message}</div>
</div>
<!-- Error session flash -->
<!--<div>--> <!--<div>-->
<!--<div class="alert alert-danger" id="errors" name="errors" style="display:none;">errors</div>--> <!--<div class="alert alert-danger" id="errors" name="errors" style="display:none;">errors</div>-->
<!--<div class="alert alert-success" id="success" name="success" style="display:none;">success</div>--> <!--<div class="alert alert-success" id="success" name="success" style="display:none;">success</div>-->
<!--</div>--> <!--</div>-->
<div metal:define-slot="left-menu"></div> <div metal:define-slot="left-menu"></div>
<div metal:define-slot="content"></div> <div metal:define-slot="content"></div>
</div> <!-- /container --> </div> <!-- /container -->
<!-- Bootstrap core JavaScript <!-- Bootstrap core JavaScript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="${home}/static/v3/js/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="${home}/static/v3/js/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="${home}/static/v3/js/jquery-ui-1.10.3.min.js"></script> <script type="text/javascript" src="${home}/static/v3/js/jquery-ui-1.10.3.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Change JQueryUI plugin names to fix name collision with Bootstrap. // Change JQueryUI plugin names to fix name collision with Bootstrap.
$.widget.bridge('uitooltip', $.ui.tooltip); $.widget.bridge('uitooltip', $.ui.tooltip);
$.widget.bridge('uibutton', $.ui.button); $.widget.bridge('uibutton', $.ui.button);
</script> </script>
<script type="text/javascript" src="${home}/static/v3/js/bootstrap/bootstrap.min.js"></script> <script type="text/javascript" src="${home}/static/v3/js/bootstrap/bootstrap.min.js"></script>
<!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/jquery.dataTables.min.js"></script> --> <!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/jquery.dataTables.min.js"></script> -->
<!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/dataTables.bootstrap.js"></script> --> <!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/dataTables.bootstrap.js"></script> -->
<!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/jquery.dataTables.ext.js"></script> --> <!-- <script type="text/javascript" src="${home}/static/v3/plugin/datatables/1.10/media/js/jquery.dataTables.ext.js"></script> -->
<!-- <script type="text/javascript" src="${home}/deform_static/scripts/deform.js"></script> --> <!-- <script type="text/javascript" src="${home}/deform_static/scripts/deform.js"></script> -->
<!-- <script type="text/javascript" src="${home}/deform_static/scripts/typeahead.min.js"></script> --> <!-- <script type="text/javascript" src="${home}/deform_static/scripts/typeahead.min.js"></script> -->
<!-- <script type="text/javascript" src="${home}/static/js/tools.js"></script> --> <!-- <script type="text/javascript" src="${home}/static/js/tools.js"></script> -->
<metal:js define-slot="js_files"></metal:js> <metal:js define-slot="js_files"></metal:js>
<script metal:define-slot="scripts"></script> <script metal:define-slot="scripts"></script>
</body> </body>
</html>
</html>
\ No newline at end of file \ No newline at end of file
import os
import colander
from deform import (Form, widget, FileData, )
from deform.interfaces import FileUploadTempStore
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from opensipkd.tools import (get_ext, dict_to_str, )
from .base_views import CSRFSchema
# from .. import get_urls
def route_list(request, p={}):
q = dict_to_str(p)
return HTTPFound(location=request.route_url('base-upload-logo', _query=q))
##########
# Unggah #
##########
# class UploadLogo(SaveFile):
# def save(self, fs):
# input_file = fs.file
# ext = get_ext(fs.filename)
# fullpath = self.create_fullpath(ext)
# output_file = open(fullpath, 'wb')
# input_file.seek(0)
# while True:
# data = input_file.read(2 << 16)
# if not data:
# break
# output_file.write(data)
# output_file.close()
# return fullpath
tmpstore = FileUploadTempStore()
class AddSchema(CSRFSchema):
upload = colander.SchemaNode(
FileData(),
widget=widget.FileUploadWidget(tmpstore),
title='Unggah')
image_for = colander.SchemaNode(
colander.String(),
widget=widget.SelectWidget(values=(('oth', "Other"), ('logo', "Logo"),
('bg', "Background"))),
title='Peruntukan')
def get_form(request, schema_cls):
schema = schema_cls()
schema = schema.bind(request=request)
return Form(schema, buttons=('simpan', 'batal'))
@view_config(route_name='base-upload-logo',
renderer='templates/form8.pt',
permission='upload-logo', require_csrf=True)
def view_file(request):
form = get_form(request, AddSchema)
if request.POST:
if 'simpan' in request.POST:
input_file = request.POST['upload'].file
filename = request.POST['upload'].filename.lower()
ext = get_ext(filename).lower()
if ext.lower() not in ['.png', '.ico']:
request.session.flash("File harus format 'png' atau 'ico'", 'error')
return dict(form=form.render())
_here = os.path.dirname(__file__)
static_path = os.path.join(os.path.dirname(_here), 'static')
fname = filename
if request.POST["image_for"] == "logo":
fname = f"logo{ext}"
elif request.POST["image_for"] == "bg":
fname = f"background{ext}"
typ = ext == '.png' and "img" or 'icon'
folder = os.path.join(static_path, typ)
if not os.path.exists(folder):
os.makedirs(folder)
fullpath = os.path.join(folder, fname)
output_file = open(fullpath, 'wb')
input_file.seek(0)
while True:
data = input_file.read(2 << 16)
if not data:
break
output_file.write(data)
request.session.flash(f"Sukses upload {fname}")
return route_list(request)
return dict(form=form.render(), scripts="")
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!