In most cases, access to an application’s (service’s) resources is granted only after users authenticate themselves in the system. Authentication is the process of checking the validity of the identifier provided by a user. A successful authentication (besides establishing a trusted relationship and generating a session key) is usually also followed up by user authorization. This second step grants the user access rights to an approved set of resources, deemed necessary for the user to perform his/her tasks.
Just like in the previous step, we’ll be placing the files of this step in the operation/version1 folder.
Let’s start by defining a data model for storing user information within a session. Create a new file named SessionUserV1.py with the following code:
src/operations/version1/SessionUserV1.ts
export class SessionUserV1 {
/* Identification */
public id: string;
public login: string;
public name: string;
public create_time: Date;
/* User information */
public time_zone: string;
public language: string;
public theme: string;
/* Security info **/
public roles: string[];
public change_pwd_time: Date;
public sites: [{ id: string, name: string }];
public settings: any;
/* Custom fields */
public custom_hdr: any;
public custom_dat: any;
}
/operations/version1/SessionUserV1.go
package operations1
import (
"time"
)
type SessionUserV1 struct {
/* Identification */
Id string `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
CreateTime time.Time `json:"create_time"`
/* User information */
TimeZone string `json:"tim_zone"`
Language string `json:"language"`
Theme string `json:"theme"`
/* Security info **/
Roles []string `json:"roles"`
ChangePwdTime time.Time `json:"change_pwd_time"`
Sites Site `json:"sites"`
Settings interface{} `json:"settings"`
/* Custom fields */
CustomHdr interface{} `json:"custom_hdr"`
CustomDat interface{} `json:"custom_dat"`
}
type Site struct {
Id string `json:"id"`
Name string `json:"name"`
}
pip_facades_sample_python/operations/version1/SessionUserV1.py
# -*- coding: utf-8 -*-
import datetime
from typing import List, Any
class SessionUserV1:
def __init__(self, id: str = None, login: str = None, name: str = None, create_time: str = None,
time_zone: str = None, language: str = None,
theme: str = None, roles: List[str] = None, change_pwd_time: datetime.datetime = None,
sites: List[dict] = None, settings: Any = None, custom_hdr: Any = None,
custom_dat: Any = None):
# Identification
self.id = id
self.login = login
self.name = name
self.create_time = create_time
# User information
self.time_zone = time_zone
self.language = language
self.theme = theme
# Security info
self.roles = roles
self.change_pwd_time = change_pwd_time
self.sites = sites
self.settings = settings
# Custom fields
self.custom_hdr = custom_hdr
self.custom_dat = custom_dat
This data model will contain all necessary information about the user: the session’s ID, login, username, list of rolls, etc.
We’ll be defining our operations for managing sessions and authenticating users in a file named SessionOperationsV1.py. A listing of this file’s code is presented below:
src/operations/version1/SessionOperationsV1.ts
import { ConfigParams } from 'pip-services4-components-node';
import { FilterParams } from 'pip-services4-data-node';
import { IReferences } from 'pip-services4-components-node';
import { Descriptor } from 'pip-services4-components-node';
import { BadRequestException } from 'pip-services4-commons-node';
import { UnauthorizedException } from 'pip-services4-commons-node';
import { HttpRequestDetector } from 'pip-services4-http-node';
import { RestOperations } from 'pip-services4-http-node';
import { ISettingsClientV1 } from '../../clients/version1/ISettingsClientV1';
import { IAccountsClientV1 } from '../../clients/version1/IAccountsClientV1';
import { AccountV1 } from '../../clients/version1/AccountV1';
import { IPasswordsClientV1 } from '../../clients/version1/IPasswordsClientV1';
import { UserPasswordInfoV1 } from '../../clients/version1/UserPasswordInfoV1';
import { IRolesClientV1 } from '../../clients/version1/IRolesClientV1';
import { ISessionsClientV1 } from '../../clients/version1/ISessionsClientV1';
import { SessionV1 } from '../../clients/version1/SessionV1';
import { IEmailSettingsClientV1 } from '../../clients/version1/IEmailSettingsClientV1';
import { EmailSettingsV1 } from '../../clients/version1/EmailSettingsV1';
import { ISitesClientV1 } from '../../clients/version1/ISitesClientV1';
import { IInvitationsClientV1 } from '../../clients/version1/IInvitationsClientV1';
import { SiteV1 } from '../../clients/version1/SiteV1';
import { SessionUserV1 } from './SessionUserV1';
export class SessionsOperationsV1 extends RestOperations {
private static _defaultConfig1 = ConfigParams.fromTuples(
'options.cookie_enabled', true,
'options.cookie', 'x-session-id',
'options.max_cookie_age', 365 * 24 * 60 * 60 * 1000
);
private _cookie: string = 'x-session-id';
private _cookieEnabled: boolean = true;
private _maxCookieAge: number = 365 * 24 * 60 * 60 * 1000;
private _settingsClient: ISettingsClientV1;
private _accountsClient: IAccountsClientV1;
private _sessionsClient: ISessionsClientV1;
private _passwordsClient: IPasswordsClientV1;
private _rolesClient: IRolesClientV1;
private _emailSettingsClient: IEmailSettingsClientV1;
private _sitesClient: ISitesClientV1;
private _invitationsClient: IInvitationsClientV1;
public constructor() {
super();
this._dependencyResolver.put('settings', new Descriptor('pip-services-settings', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('accounts', new Descriptor('pip-services-accounts', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('passwords', new Descriptor('pip-services-passwords', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('roles', new Descriptor('pip-services-roles', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('emailsettings', new Descriptor('pip-services-emailsettings', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('sessions', new Descriptor('pip-services-sessions', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('sites', new Descriptor('pip-services-sites', 'client', '*', '*', '1.0'));
this._dependencyResolver.put('invitations', new Descriptor('pip-services-invitations', 'client', '*', '*', '1.0'));
}
public configure(config: ConfigParams): void {
config = config.setDefaults(SessionsOperationsV1._defaultConfig1);
this._dependencyResolver.configure(config);
this._cookieEnabled = config.getAsBooleanWithDefault('options.cookie_enabled', this._cookieEnabled);
this._cookie = config.getAsStringWithDefault('options.cookie', this._cookie);
this._maxCookieAge = config.getAsLongWithDefault('options.max_cookie_age', this._maxCookieAge);
}
public setReferences(references: IReferences): void {
super.setReferences(references);
this._settingsClient = this._dependencyResolver.getOneRequired<ISettingsClientV1>('settings');
this._sessionsClient = this._dependencyResolver.getOneRequired<ISessionsClientV1>('sessions');
this._accountsClient = this._dependencyResolver.getOneRequired<IAccountsClientV1>('accounts');
this._passwordsClient = this._dependencyResolver.getOneRequired<IPasswordsClientV1>('passwords');
this._rolesClient = this._dependencyResolver.getOneRequired<IRolesClientV1>('roles');
this._emailSettingsClient = this._dependencyResolver.getOneOptional<IEmailSettingsClientV1>('emailsettings');
this._sitesClient = this._dependencyResolver.getOneRequired<ISitesClientV1>('sites');
this._invitationsClient = this._dependencyResolver.getOneRequired<IInvitationsClientV1>('invitations');
}
public async loadSession(req: any, res: any, next: () => void): Promise<void> {
// parse headers first, and if nothing in headers get cookie
let sessionId = req.headers['x-session-id'];
if (sessionId) {
let session = await this._sessionsClient.getSessionById('facade', sessionId);
if (session == null) {
let err = new UnauthorizedException(
'facade',
'SESSION_NOT_FOUND',
'Session invalid or already expired.'
).withDetails('session_id', sessionId).withStatus(440);
this.sendError(req, res, err);
} else {
// Associate session user with the request
req.user_id = session.user_id;
req.user_name = session.user_name;
req.user = session.user;
req.session_id = session.id;
next();
}
} else {
next();
}
}
public async openSession(req: any, res: any, account: AccountV1, roles: string[]): Promise<void> {
let session: SessionV1;
let sites: SiteV1[];
let passwordInfo: UserPasswordInfoV1;
let settings: ConfigParams;
try {
let siteRoles = roles.filter(r => r.indexOf(':') > 0);
let siteIds = siteRoles.map((r) => {
let pos = r.indexOf(':');
return pos >= 0 ? r.substring(0, pos) : r;
});
if (siteIds.length > 0) {
let filter = FilterParams.fromTuples('ids', siteIds);
let page = await this._sitesClient.getSites(null, filter, null);
sites = page != null ? page.data : [];
} else {
sites = [];
}
passwordInfo = await this._passwordsClient.getPasswordInfo(null, account.id);
settings = await this._settingsClient.getSectionById(null, account.id);
// Open a new user session
let user = <SessionUserV1>{
id: account.id,
name: account.name,
login: account.login,
create_time: account.create_time,
time_zone: account.time_zone,
language: account.language,
theme: account.theme,
roles: roles,
sites: sites.map(s => { return { id: s.id, name: s.name } }),
settings: settings,
change_pwd_time: passwordInfo != null ? passwordInfo.change_time : null,
custom_hdr: account.custom_hdr,
custom_dat: account.custom_dat
};
let address = HttpRequestDetector.detectAddress(req);
let client = HttpRequestDetector.detectBrowser(req);
let platform = HttpRequestDetector.detectPlatform(req);
session = await this._sessionsClient.openSession(
null, account.id, account.name,
address, client, user, null
);
this.sendResult(req, res, session);
} catch (err) {
this.sendError(req, res, err);
}
}
public async signup(req: any, res: any): Promise<void> {
let signupData = req.body;
let account: AccountV1 = null;
let invited: boolean = false;
let roles: string[] = [];
try {
// Validate password first
// Todo: complete implementation after validate password is added
// Create account
let newAccount = <AccountV1>{
name: signupData.name,
login: signupData.login || signupData.email, // Use email by default
language: signupData.language,
theme: signupData.theme,
time_zone: signupData.time_zone
};
account = await this._accountsClient.createAccount(
null, newAccount
)
// Create password for the account
let password = signupData.password;
this._passwordsClient.setPassword(
null, account.id, password
);
// Activate all pending invitations
let email = signupData.email;
let invitations = await this._invitationsClient.activateInvitations(
null, email, account.id);
if (invitations) {
// Calculate user roles from activated invitations
for (let invitation of invitations) {
// Was user invited with the same email?
invited = invited || email == invitation.invitee_email;
if (invitation.site_id) {
invitation.role = invitation.role || 'user';
let role = invitation.site_id + ':' + invitation.role;
roles.push(role);
}
}
}
// Create email settings for the account
let newEmailSettings = <EmailSettingsV1>{
id: account.id,
name: account.name,
email: email,
language: account.language
};
if (this._emailSettingsClient != null) {
if (invited) {
this._emailSettingsClient.setVerifiedSettings(
null, newEmailSettings
);
} else {
this._emailSettingsClient.setSettings(
null, newEmailSettings
);
}
}
await this.openSession(req, res, account, roles);
}
catch (err) {
this.sendError(req, res, err);
}
}
public async signupValidate(req: any, res: any): Promise<void> {
let login = req.param('login');
if (login) {
let account = await this._accountsClient.getAccountByIdOrLogin(null, login);
if (account != null) {
let err = new BadRequestException(
null, 'LOGIN_ALREADY_USED',
'Login ' + login + ' already being used'
).withDetails('login', login);
this.sendError(req, res, err);
} else this.sendEmptyResult(req, res);
}
else {
this.sendEmptyResult(req, req);
}
}
public async signin(req: any, res: any): Promise<void> {
let login = req.param('login');
let password = req.param('password');
let account: AccountV1;
let roles: string[] = [];
try {
// Find user account
account = await this._accountsClient.getAccountByIdOrLogin(null, login);
if (account == null) {
throw new BadRequestException(
null,
'WRONG_LOGIN',
'Account ' + login + ' was not found'
).withDetails('login', login);
}
// Authenticate user
let result = await this._passwordsClient.authenticate(null, account.id, password);
// wrong password error is UNKNOWN when use http client
if (result == false) {
throw new BadRequestException(
null,
'WRONG_PASSWORD',
'Wrong password for account ' + login
).withDetails('login', login);
}
// Retrieve user roles
if (this._rolesClient) {
roles = await this._rolesClient.getRolesById(null, account.id);
}
await this.openSession(req, res, account, roles);
} catch (err) {
this.sendError(req, res, err);
}
}
public async signout(req: any, res: any): Promise<void> {
if (req.session_id) {
try {
await this._sessionsClient.closeSession(null, req.session_id);
this.sendEmptyResult(req, res)
} catch (err) {
this.sendError(req, res, err);
}
} else {
this.sendEmptyResult(req, res)
}
}
public async getSessions(req: any, res: any): Promise<void> {
let filter = this.getFilterParams(req);
let paging = this.getPagingParams(req);
let sessions = await this._sessionsClient.getSessions(
null, filter, paging
);
this.sendResult(req, res, sessions);
}
public async restoreSession(req: any, res: any): Promise<void> {
let sessionId = req.param('session_id');
let session = await this._sessionsClient.getSessionById(null, sessionId);
// If session closed then return null
if (session && !session.active)
session = null;
if (session)
this.sendResult(req, res, session);
else
this.sendEmptyResult(req, res);
}
public async getUserSessions(req: any, res: any): Promise<void> {
let filter = this.getFilterParams(req);
let paging = this.getPagingParams(req);
let userId = req.params.user_id || req.params.account_id;
filter.setAsObject('user_id', userId);
let result = await this._sessionsClient.getSessions(
null, filter, paging
);
this.sendResult(req, res, result);
}
public async getCurrentSession(req: any, res: any): Promise<void> {
// parse headers first, and if nothing in headers get cookie
let sessionId = req.headers['x-session-id'];
let result = await this._sessionsClient.getSessionById(null, sessionId);
this.sendResult(req, res, result);
}
public async closeSession(req: any, res: any): Promise<void> {
let sessionId = req.params.session_id || req.param('session_id');
let result = await this._sessionsClient.closeSession(null, sessionId);
this.sendResult(req, res, result);
}
}
/operations/version1/SessionOperationsV1.go
package operations1
import (
"context"
"encoding/json"
"net/http"
"sync"
"time"
accclients1 "github.com/pip-services-users/pip-clients-accounts-go/version1"
passclients1 "github.com/pip-services-users/pip-clients-passwords-go/version1"
roleclients1 "github.com/pip-services-users/pip-clients-roles-go/version1"
sessclients1 "github.com/pip-services-users/pip-clients-sessions-go/version1"
cdata "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/data"
cerr "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/errors"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
httpcontroller "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
)
type SessionsOperationsV1 struct {
*httpcontroller.RestOperations
defaultConfig *cconf.ConfigParams
cookie string
cookieEnabled bool
maxCookieAge int64
accountsClient accclients1.IAccountsClientV1
sessionsClient sessclients1.ISessionsClientV1
passwordsClient passclients1.IPasswordsClientV1
rolesClient roleclients1.IRolesClientV1
}
func NewSessionsOperationsV1(ctx context.Context) *SessionsOperationsV1 {
c := SessionsOperationsV1{
RestOperations: httpcontroller.NewRestOperations(),
}
c.defaultConfig = cconf.NewConfigParamsFromTuples(
"options.cookie_enabled", true,
"options.cookie", "x-session-id",
"options.max_cookie_age", 365*24*60*60*1000,
)
c.cookie = "x-session-id"
c.cookieEnabled = true
c.maxCookieAge = 365 * 24 * 60 * 60 * 1000
c.DependencyResolver.Put(ctx, "accounts", cref.NewDescriptor("pip-services-accounts", "client", "*", "*", "1.0"))
c.DependencyResolver.Put(ctx, "passwords", cref.NewDescriptor("pip-services-passwords", "client", "*", "*", "1.0"))
c.DependencyResolver.Put(ctx, "roles", cref.NewDescriptor("pip-services-roles", "client", "*", "*", "1.0"))
c.DependencyResolver.Put(ctx, "sessions", cref.NewDescriptor("pip-services-sessions", "client", "*", "*", "1.0"))
return &c
}
func (c *SessionsOperationsV1) Configure(ctx context.Context, config *cconf.ConfigParams) {
config = config.SetDefaults(c.defaultConfig)
c.DependencyResolver.Configure(ctx, config)
c.cookieEnabled = config.GetAsBooleanWithDefault("options.cookie_enabled", c.cookieEnabled)
c.cookie = config.GetAsStringWithDefault("options.cookie", c.cookie)
c.maxCookieAge = config.GetAsLongWithDefault("options.max_cookie_age", c.maxCookieAge)
}
func (c *SessionsOperationsV1) SetReferences(ctx context.Context, references cref.IReferences) {
c.RestOperations.SetReferences(ctx, references)
dependency, _ := c.DependencyResolver.GetOneRequired("sessions")
sesionsClient, ok1 := dependency.(sessclients1.ISessionsClientV1)
if !ok1 {
panic("SessionOperationsV1: Cant't resolv dependency 'client' to ISessionsClientV1")
}
c.sessionsClient = sesionsClient
dependency, _ = c.DependencyResolver.GetOneRequired("accounts")
acountClient, ok2 := dependency.(accclients1.IAccountsClientV1)
if !ok2 {
panic("SessionOperationsV1: Cant't resolv dependency 'client' to IAccountsClientV1")
}
c.accountsClient = acountClient
dependency, _ = c.DependencyResolver.GetOneRequired("passwords")
passClient, ok3 := dependency.(passclients1.IPasswordsClientV1)
if !ok3 {
panic("SessionOperationsV1: Cant't resolv dependency 'client' to IPasswordsClientV1")
}
c.passwordsClient = passClient
dependency, _ = c.DependencyResolver.GetOneRequired("roles")
rolesClient, ok4 := dependency.(roleclients1.IRolesClientV1)
if !ok4 {
panic("SessionOperationsV1: Cant't resolv dependency 'client' to IRolesClientV1")
}
c.rolesClient = rolesClient
}
func (c *SessionsOperationsV1) LoadSession(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
sessionId := req.Header.Get("x-session-id")
if sessionId != "" {
session, err := c.sessionsClient.GetSessionById("facade", sessionId)
if session == nil && err == nil {
err = cerr.NewUnauthorizedError(
"facade",
"SESSION_NOT_FOUND",
"Session invalid or already expired.",
).WithDetails("session_id", sessionId).WithStatus(440)
}
if err == nil {
// Associate session user with the request
buf, _ := json.Marshal(session.User)
user := make(map[string]interface{}, 0)
json.Unmarshal(buf, &user)
req = req.WithContext(context.WithValue(req.Context(), "user_id", session.UserId))
req = req.WithContext(context.WithValue(req.Context(), "user_name", session.UserName))
req = req.WithContext(context.WithValue(req.Context(), "user", *cdata.NewAnyValueMapFromValue(user)))
req = req.WithContext(context.WithValue(req.Context(), "session_id", session.Id))
next.ServeHTTP(res, req)
} else {
c.SendError(res, req, err)
}
} else {
next.ServeHTTP(res, req)
}
}
func (c *SessionsOperationsV1) OpenSession(res http.ResponseWriter, req *http.Request, account *accclients1.AccountV1, roles []string) {
var session *sessclients1.SessionV1
var settings cconf.ConfigParams
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
passwordInfo, err := c.passwordsClient.GetPasswordInfo("", account.Id)
if err != nil {
c.SendError(res, req, err)
return
}
var changePwdTime time.Time
if passwordInfo != nil {
changePwdTime = passwordInfo.ChangeTime
}
var user = SessionUserV1{
Id: account.Id,
Name: account.Name,
Login: account.Login,
CreateTime: account.CreateTime,
TimeZone: account.TimeZone,
Language: account.Language,
Theme: account.Theme,
Roles: roles,
Settings: settings,
ChangePwdTime: changePwdTime,
CustomHdr: account.CustomHdr,
CustomDat: account.CustomDat,
}
address := httpcontroller.HttpRequestDetector.DetectAddress(req)
client := httpcontroller.HttpRequestDetector.DetectBrowser(req)
session, err = c.sessionsClient.OpenSession("", account.Id, account.Name, address, client, user, nil)
if err != nil {
c.SendError(res, req, err)
return
}
//res.json(session)
c.SendResult(res, req, session, nil)
}()
wg.Wait()
}
func (c *SessionsOperationsV1) Signup(res http.ResponseWriter, req *http.Request) {
signupData := make(map[string]interface{})
c.DecodeBody(req, &signupData)
r, ok := signupData["roles"].([]string)
roles := make([]string, 0)
if ok {
roles = append(roles, r...)
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
// Use email by default
login, ok := signupData["login"].(string)
if !ok {
login, _ = signupData["email"].(string)
}
// Create account
newAccount := accclients1.AccountV1{}
newAccount.Name, _ = signupData["name"].(string)
newAccount.Login = login
newAccount.Language, _ = signupData["language"].(string)
newAccount.Theme, _ = signupData["theme"].(string)
newAccount.TimeZone, _ = signupData["time_zone"].(string)
account, err := c.accountsClient.CreateAccount("", &newAccount)
if err != nil {
c.SendError(res, req, err)
return
}
// Create password for the account
password, _ := signupData["password"].(string)
err = c.passwordsClient.SetPassword(
"", account.Id, password)
if err != nil {
c.SendError(res, req, err)
return
}
// Create roles for the account
if len(roles) > 0 {
c.rolesClient.GrantRoles(
"", account.Id, roles)
}
c.OpenSession(res, req, account, roles)
}()
wg.Wait()
}
func (c *SessionsOperationsV1) Signin(res http.ResponseWriter, req *http.Request) {
login := c.GetParam(req, "login")
password := c.GetParam(req, "password")
if login == "" && password == "" {
params := make(map[string]string, 0)
c.DecodeBody(req, ¶ms)
login = params["login"]
password = params["password"]
}
roles := make([]string, 0)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
// Find user account
account, err := c.accountsClient.GetAccountByIdOrLogin("", login)
if err != nil {
c.SendError(res, req, err)
return
}
if err == nil && account == nil {
err = cerr.NewBadRequestError(
"",
"WRONG_LOGIN",
"Account "+login+" was not found",
).WithDetails("login", login)
if err != nil {
c.SendError(res, req, err)
return
}
}
// Authenticate user
result, err := c.passwordsClient.Authenticate("", account.Id, password)
// wrong password error is UNKNOWN when use http client
if (err == nil && result == false) || (err != nil && err.(*cerr.ApplicationError).Cause == "Invalid password") {
err = cerr.NewBadRequestError(
"",
"WRONG_PASSWORD",
"Wrong password for account "+login,
).WithDetails("login", login)
if err != nil {
c.SendError(res, req, err)
return
}
}
// Retrieve user roles
if c.rolesClient != nil {
roles, err = c.rolesClient.GetRolesById("", account.Id)
if err != nil {
c.SendError(res, req, err)
return
}
} else {
roles = make([]string, 0)
}
c.OpenSession(res, req, account, roles)
}()
wg.Wait()
}
func (c *SessionsOperationsV1) Signout(res http.ResponseWriter, req *http.Request) {
sessionId, ok := req.Context().Value("session_id").(string)
if ok {
_, err := c.sessionsClient.CloseSession("", sessionId)
if err != nil {
c.SendError(res, req, err)
} else {
// res.json(204);
c.SendEmptyResult(res, req, nil)
}
} else {
//res.json(204);
c.SendEmptyResult(res, req, nil)
}
}
pip_facades_sample_python/operations/version1/SessionOperationsV1.py
# -*- coding: utf-8 -*-
from typing import List
import bottle
from pip_services4_components.config import ConfigParams
from pip_services4_commons.convert import JsonConverter, TypeCode
from pip_services4_data.query import FilterParams
from pip_services4_commons.errors import UnauthorizedException, BadRequestException
from pip_services4_components.refer import IReferences, Descriptor
from pip_services4_http.controller import RestOperations
from pip_services4_http.controller.HttpRequestDetector import HttpRequestDetector
from pip_facades_sample_python.clients.version1.AccountV1 import AccountV1
from pip_facades_sample_python.clients.version1.EmailSettingsV1 import EmailSettingsV1
from pip_facades_sample_python.clients.version1.IAccountsClientV1 import IAccountsClientV1
from pip_facades_sample_python.clients.version1.IEmailSettingsClientV1 import IEmailSettingsClientV1
from pip_facades_sample_python.clients.version1.IInvitationsClientV1 import IInvitationsClientV1
from pip_facades_sample_python.clients.version1.IPasswordsClientV1 import IPasswordsClientV1
from pip_facades_sample_python.clients.version1.IRolesClientV1 import IRolesClientV1
from pip_facades_sample_python.clients.version1.ISessionsClientV1 import ISessionsClientV1
from pip_facades_sample_python.clients.version1.ISettingsClientV1 import ISettingsClientV1
from pip_facades_sample_python.clients.version1.ISitesClientV1 import ISitesClientV1
from pip_facades_sample_python.clients.version1.SessionV1 import SessionV1
from pip_facades_sample_python.clients.version1.SiteV1 import SiteV1
from pip_facades_sample_python.clients.version1.UserPasswordInfoV1 import UserPasswordInfoV1
from pip_facades_sample_python.operations.version1.SessionUserV1 import SessionUserV1
class SessionsOperationsV1(RestOperations):
__default_config = ConfigParams.from_tuples(
'options.cookie_enabled', True,
'options.cookie', 'x-session-id',
'options.max_cookie_age', 365 * 24 * 60 * 60 * 1000
)
def __init__(self):
super().__init__()
self.__cookie: str = 'x-session-id'
self.__cookie_enabled: bool = True
self.__max_cookie_age: float = 365 * 24 * 60 * 60 * 1000
self.__settings_client: ISettingsClientV1 = None
self.__accounts_client: IAccountsClientV1 = None
self.__sessions_client: ISessionsClientV1 = None
self.__passwords_client: IPasswordsClientV1 = None
self.__roles_client: IRolesClientV1 = None
self.__email_settings_client: IEmailSettingsClientV1 = None
self.__sites_client: ISitesClientV1 = None
self.__invitations_client: IInvitationsClientV1 = None
self._dependency_resolver.put('settings', Descriptor('pip-services-settings', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('accounts', Descriptor('pip-services-accounts', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('passwords', Descriptor('pip-services-passwords', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('roles', Descriptor('pip-services-roles', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('emailsettings',
Descriptor('pip-services-emailsettings', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('sessions', Descriptor('pip-services-sessions', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('sites', Descriptor('pip-services-sites', 'client', '*', '*', '1.0'))
self._dependency_resolver.put('invitations', Descriptor('pip-services-invitations', 'client', '*', '*', '1.0'))
def configure(self, config: ConfigParams):
config = config.set_defaults(SessionsOperationsV1.__default_config)
self._dependency_resolver.configure(config)
self.__cookie_enabled = config.get_as_boolean_with_default('options.cookie_enabled', self.__cookie_enabled)
self.__cookie = config.get_as_string_with_default('options.cookie', self.__cookie)
self.__max_cookie_age = config.get_as_long_with_default('options.max_cookie_age', self.__max_cookie_age)
def set_references(self, references: IReferences):
super().set_references(references)
self.__settings_client = self._dependency_resolver.get_one_required('settings')
self.__sessions_client = self._dependency_resolver.get_one_required('sessions')
self.__accounts_client = self._dependency_resolver.get_one_required('accounts')
self.__passwords_client = self._dependency_resolver.get_one_required('passwords')
self.__roles_client = self._dependency_resolver.get_one_required('roles')
self.__email_settings_client = self._dependency_resolver.get_one_required('emailsettings')
self.__sites_client = self._dependency_resolver.get_one_required('sites')
self.__invitations_client = self._dependency_resolver.get_one_required('invitations')
def load_session(self):
# parse headers first, and if nothing in headers get cookie
session_id = bottle.request.headers.get('x-session-id')
session, err = None, None
if session_id:
try:
session = self.__sessions_client.get_session_by_id('facade', session_id)
if session is None:
raise UnauthorizedException(
'facade',
'SESSION_NOT_FOUND',
'Session invalid or already expired.'
).with_details('session_id', session_id).with_status(440)
except Exception as err:
return self._send_error(err)
if err is None and session:
# Associate session user with the request
bottle.request.user_id = session.user_id
bottle.request.user_name = session.user_name
bottle.request.user = session.user
bottle.request.session_id = session.id
else:
return self._send_error(err)
def open_session(self, account: AccountV1, roles: List[str], ):
try:
session: SessionV1 = None
sites: List[SiteV1] = None
passwordInfo: UserPasswordInfoV1 = None
settings: ConfigParams = None
# Retrieve sites for user
site_roles = [] if not roles else list(filter(lambda x: x.find(':') > 0, roles))
site_ids = [] if not site_roles else list(map(lambda x: x[0:x.find(':')] if x.find(':') >= 0 else x, site_roles))
if len(site_ids) > 0:
filter_params = FilterParams.from_tuples('ids', site_ids)
page = self.__sites_client.get_sites(None, filter_params, None)
sites = [] if page is None else page.data
else:
sites = []
password_info = self.__passwords_client.get_password_info(None, account.id)
settings = self.__settings_client.get_section_by_id(None, account.id)
# Open a new user session
user = SessionUserV1(
id=account.id,
name=account.name,
login=account.login,
create_time=account.create_time,
time_zone=account.time_zone,
language=account.language,
theme=account.theme,
roles=roles,
sites=list(map(lambda x: {'id': x.id, 'name': x.name}, sites)),
settings=settings,
change_pwd_time=None if password_info is None else password_info.change_time,
custom_hdr=account.custom_hdr,
custom_dat=account.custom_dat
)
address = HttpRequestDetector.detect_address(bottle.request)
client = HttpRequestDetector.detect_browser(bottle.request)
platform = HttpRequestDetector.detect_platform(bottle.request)
session = self.__sessions_client.open_session(None, account.id, account.name, address, client, user, None)
return JsonConverter.to_json(session)
except Exception as err:
return self._send_error(err)
def signup(self):
try:
signup_data = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(
TypeCode.Map, bottle.request.json)
account: AccountV1 = None
invited: bool = False
roles: List[str] = []
# Validate password first
# Todo: complete implementation after validate password is added
# Create account
new_account = AccountV1(
name=signup_data.get('name'),
login=signup_data.get('login') or signup_data.get('email'),
language=signup_data.get('language'),
theme=signup_data.get('theme'),
time_zone=signup_data.get('time_zone')
)
account = self.__accounts_client.create_account(None, new_account)
# Create password for the account
password = signup_data.get('password')
self.__passwords_client.set_password(None, account.id, password)
# Activate all pending invitations
email = signup_data.get('email')
invitations = self.__invitations_client.activate_invitations(None, email, account.id)
if invitations:
# Calculate user roles from activated invitations
for invitation in invitations:
# Was user invited with the same email?
invited = invited or email == invitation.invitee_email
if invitation.site_id:
invitation.role = invitation.role or 'user'
role = invitation.site_id + ':' + invitation.role
roles.append(role)
# Create email settings for the account
new_email_settings = EmailSettingsV1(
id=account.id,
name=account.name,
email=email,
language=account.language
)
if self.__email_settings_client is not None:
if invited:
self.__email_settings_client.set_verified_settings(None, new_email_settings)
else:
self.__email_settings_client.set_settings(None, new_email_settings)
return self.open_session(account, roles)
except Exception as err:
return self._send_error(err)
def signup_validate(self):
login = dict(bottle.request.query.decode()).get('login')
if login:
try:
account = self.__accounts_client.get_account_by_id_or_login(None, login)
if account:
raise BadRequestException(
None, 'LOGIN_ALREADY_USED',
'Login ' + login + ' already being used'
).with_details('login', login)
return bottle.HTTPResponse(status=204)
except Exception as err:
return self._send_error(err)
else:
return bottle.HTTPResponse(status=204)
def signin(self):
json_data = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(
TypeCode.Map, bottle.request.json)
login = json_data.get('login')
password = json_data.get('password')
account: AccountV1 = None
roles: List[str] = []
# Find user account
try:
account = self.__accounts_client.get_account_by_id_or_login(None, login)
if account is None:
raise BadRequestException(
None,
'WRONG_LOGIN',
'Account ' + login + ' was not found'
).with_details('login', login)
# Authenticate user
result = self.__passwords_client.authenticate(None, account.id, password)
# wrong password error is UNKNOWN when use http client
if result is False:
raise BadRequestException(
None,
'WRONG_PASSWORD',
'Wrong password for account ' + login
).with_details('login', login)
# Retrieve user roles
if self.__roles_client:
roles = self.__roles_client.get_roles_by_id(None, account.id, )
else:
roles = []
return self.open_session(account, roles)
except Exception as err:
return self._send_error(err)
def signout(self):
if hasattr(bottle.request, 'session_id'):
try:
self.__sessions_client.close_session(None, bottle.request.session_id)
return bottle.HTTPResponse(status=204)
except Exception as err:
self._send_error(err)
return bottle.HTTPResponse(status=204)
def get_sessions(self):
filter_params = self._get_filter_params()
paging = self._get_paging_params()
result = self.__sessions_client.get_sessions(None, filter_params, paging)
return self._send_result(result)
def restore_session(self):
try:
session_id = dict(bottle.request.query.decode()).get('session_id')
session = self.__sessions_client.get_session_by_id(None, session_id)
# If session closed then return null
if session and not session.active:
session = None
if session:
return JsonConverter.to_json(session)
else:
return bottle.HTTPResponse(status=204)
except Exception as err:
return self._send_error(err)
def get_user_sessions(self, user_id):
filter_params = self._get_filter_params()
paging = self._get_paging_params()
filter_params.set_as_object('user_id', user_id)
sessions = self.__sessions_client.get_sessions(None, filter_params, paging)
return self._send_result(sessions.to_json())
def get_current_session(self):
# parse headers first, and if nothing in headers get cookie
session_id = bottle.request.headers.get('x-session-id')
session = self.__sessions_client.get_session_by_id(None, session_id)
return self._send_result(session)
def close_session(self, user_id, session_id):
result = self.__sessions_client.close_session(None, session_id)
return self._send_result(result)
This class contains the main operations for managing sessions, which allow us to load existing sessions, create new sessions, close existing sessions, and also authenticate users. This class depends on four microservices - Accounts, Passwords, Roles, and Sessions - each of which is responsible for a certain part of the class’s logic.
The signup and signin methods perform registration of new users and authentication of existing ones, respectively. Upon successful registration or authorization, the handlers of these operations open a new session as the finishing step of these methods.
The signout method closes sessions when users leave the system, or automatically when the session expires.
The load_session interceptor checks a session’s validity, and whether or not it even exists. The interceptor checks the request’s header for a session ID and, if one is found, uses it to retrieve data about the session from the Sessions microservice. If the session is expired or incorrect, an error will be returned. If everything’s all right, information about the user and the session are extracted and attached to the request, and a “green light” is given for further processing.
To perform these operations, our microservice needs to be able to interact with the microservices it depends on. This communication is made simple using standard clients, the links to which are set in the setReferences method.
The authorization mechanism will be responsible for limiting access to resources, depending on the roles a user has been assigned. This part’s implementation will be discussed in Step 5 - Authorization.