Log PlexPy logins to database

This commit is contained in:
JonnyWong16 2016-05-13 21:43:21 -07:00
parent 27716d080f
commit 8ed7688277
8 changed files with 1018 additions and 7 deletions

View file

@ -0,0 +1,127 @@
login_table_options = {
"destroy": true,
"language": {
"search": "Search: ",
"lengthMenu": "Show _MENU_ entries per page",
"info": "Showing _START_ to _END_ of _TOTAL_ results",
"infoEmpty": "Showing 0 to 0 of 0 entries",
"infoFiltered": "(filtered from _MAX_ total entries)",
"emptyTable": "No data in table",
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
},
"stateSave": true,
"pagingType": "full_numbers",
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [0, 'desc'],
"autoWidth": false,
"columnDefs": [
{
"targets": [0],
"data": "timestamp",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
date = moment(cellData, "X").format(date_format);
$(td).html(date);
} else {
$(td).html(cellData);
}
},
"searchable": false,
"width": "10%",
"className": "no-wrap"
},
{
"targets": [1],
"data": "timestamp",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
time = moment(cellData, "X").format(time_format);
$(td).html(time);
} else {
$(td).html(cellData);
}
},
"searchable": false,
"width": "10%",
"className": "no-wrap hidden-sm hidden-xs"
},
{
"targets": [2],
"data": "ip_address",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData) {
if (isPrivateIP(cellData)) {
if (cellData != '') {
$(td).html(cellData);
} else {
$(td).html('n/a');
}
} else {
external_ip = '<span class="external-ip-tooltip" data-toggle="tooltip" title="External IP"><i class="fa fa-map-marker fa-fw"></i></span>';
$(td).html('<a href="javascript:void(0)" data-toggle="modal" data-target="#ip-info-modal">' + external_ip + cellData + '</a>');
}
} else {
$(td).html('n/a');
}
},
"width": "20%",
"className": "no-wrap modal-control-ip"
},
{
"targets": [3],
"data": "host",
"width": "20%",
"className": "no-wrap"
},
{
"targets": [4],
"data": "os",
"width": "20%",
"className": "no-wrap"
},
{
"targets": [5],
"data": "browser",
"width": "20%",
"className": "no-wrap"
}
],
"drawCallback": function (settings) {
// Jump to top of page
// $('html,body').scrollTop(0);
$('#ajaxMsg').fadeOut();
// Create the tooltips.
$('.external-ip-tooltip').tooltip({ container: 'body' });
},
"preDrawCallback": function (settings) {
var msg = "<i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...";
showMsg(msg, false, false, 0)
}
}
$('.login_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
var tr = $(this).closest('tr');
var row = login_table.row(tr);
var rowData = row.data();
function getUserLocation(ip_address) {
if (isPrivateIP(ip_address)) {
return "n/a"
} else {
$.ajax({
url: 'get_ip_address_details',
data: { ip_address: ip_address },
async: true,
complete: function (xhr, status) {
$("#ip-info-modal").html(xhr.responseText);
}
});
}
}
getUserLocation(rowData['ip_address']);
});

View file

@ -66,6 +66,7 @@ DOCUMENTATION :: END
<li><a id="ip-tab-btn" href="#userAddresses" data-toggle="tab">IP Addresses</a></li>
<li><a id="history-tab-btn" href="#userHistory" data-toggle="tab">History</a></li>
<li><a id="sync-tab-btn" href="#userSyncItems" data-toggle="tab">Synced Items</a></li>
<li><a id="login-tab-btn" href="#userLogins" data-toggle="tab">PlexPy Logins</a></li>
</ul>
</div>
</div>
@ -270,6 +271,41 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div class="tab-pane" id="userLogins">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class='table-card-header'>
<div class="header-bar">
<span>
<i class="fa fa-sign-in"></i> PlexPy Login for <strong>
<span class="set-username">${data['friendly_name']}</span>
</strong>
</span>
</div>
<div class="button-bar">
<div class="btn-group colvis-button-bar hidden-xs" id="button-bar-login"></div>
</div>
</div>
<div class="table-card-back">
<table class="display login_table" id="login_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left" id="date">Date</th>
<th align="left" id="time">Time</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="host">Host</th>
<th align="left" id="os">Operating System</th>
<th align="left" id="browser">Browser</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
@ -331,6 +367,7 @@ DOCUMENTATION :: END
<script src="${http_root}js/tables/history_table.js"></script>
<script src="${http_root}js/tables/user_ips.js"></script>
<script src="${http_root}js/tables/sync_table.js"></script>
<script src="${http_root}js/tables/login_table.js"></script>
<script>
$(document).ready(function () {
@ -433,6 +470,22 @@ DOCUMENTATION :: END
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
$( "#login-tab-btn" ).one( "click", function() {
// Build user login table
login_table_options.ajax = {
url: 'get_user_logins',
data: function(d) {
d.user_id = user_id;
}
}
login_table = $('#login_table-UID-${data["user_id"]}').DataTable(login_table_options);
var colvis_login = new $.fn.dataTable.ColVis( login_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_login.button() ).appendTo('#button-bar-login');
clearSearchButton('login_table-UID-${data["user_id"]}', login_table);
});
% if _session['user_group'] == 'admin':
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {

View file

@ -0,0 +1,675 @@
"""
Extract client information from http user agent
The module does not try to detect all capabilities of browser in current form (it can easily be extended though).
Tries to
* be fast
* very easy to extend
* reliable enough for practical purposes
* assist python web apps to detect clients.
"""
__version__ = '1.7.8'
class DetectorsHub(dict):
_known_types = ['os', 'dist', 'flavor', 'browser']
def __init__(self, *args, **kw):
dict.__init__(self, *args, **kw)
for typ in self._known_types:
self.setdefault(typ, [])
self.registerDetectors()
def register(self, detector):
if detector.info_type not in self._known_types:
self[detector.info_type] = [detector]
self._known_types.insert(detector.order, detector.info_type)
else:
self[detector.info_type].append(detector)
def __iter__(self):
return iter(self._known_types)
def registerDetectors(self):
detectors = [v() for v in globals().values() if DetectorBase in getattr(v, '__mro__', [])]
for d in detectors:
if d.can_register:
self.register(d)
class DetectorBase(object):
name = "" # "to perform match in DetectorsHub object"
info_type = "override me"
result_key = "override me"
order = 10 # 0 is highest
look_for = "string to look for"
skip_if_found = [] # strings if present stop processin
can_register = False
version_markers = [("/", " ")]
allow_space_in_version = False
_suggested_detectors = None
platform = None
bot = False
def __init__(self):
if not self.name:
self.name = self.__class__.__name__
self.can_register = (self.__class__.__dict__.get('can_register', True))
def detect(self, agent, result):
# -> True/None
word = self.checkWords(agent)
if word:
result[self.info_type] = dict(name=self.name)
result['bot'] = self.bot
version = self.getVersion(agent, word)
if version:
result[self.info_type]['version'] = version
if self.platform:
result['platform'] = {'name': self.platform, 'version': version}
return True
def checkWords(self, agent):
# -> True/None
for w in self.skip_if_found:
if w in agent:
return False
if isinstance(self.look_for, (tuple, list)):
for word in self.look_for:
if word in agent:
return word
elif self.look_for in agent:
return self.look_for
def getVersion(self, agent, word):
"""
=> version string /None
"""
version_markers = self.version_markers if \
isinstance(self.version_markers[0], (list, tuple)) else [self.version_markers]
version_part = agent.split(word, 1)[-1]
for start, end in version_markers:
if version_part.startswith(start) and end in version_part:
version = version_part[1:]
if end: # end could be empty string
version = version.split(end)[0]
if not self.allow_space_in_version:
version = version.split()[0]
return version
class OS(DetectorBase):
info_type = "os"
can_register = False
version_markers = [";", " "]
allow_space_in_version = True
platform = None
class Dist(DetectorBase):
info_type = "dist"
can_register = False
platform = None
class Flavor(DetectorBase):
info_type = "flavor"
can_register = False
platform = None
class Browser(DetectorBase):
info_type = "browser"
can_register = False
class Firefox(Browser):
look_for = "Firefox"
version_markers = [('/', '')]
skip_if_found = ["SeaMonkey", "web/snippet"]
class SeaMonkey(Browser):
look_for = "SeaMonkey"
version_markers = [('/', '')]
class Konqueror(Browser):
look_for = "Konqueror"
version_markers = ["/", ";"]
class OperaMobile(Browser):
look_for = "Opera Mobi"
name = "Opera Mobile"
def getVersion(self, agent, word):
try:
look_for = "Version"
return agent.split(look_for)[1][1:].split(' ')[0]
except IndexError:
look_for = "Opera"
return agent.split(look_for)[1][1:].split(' ')[0]
class Opera(Browser):
look_for = "Opera"
def getVersion(self, agent, word):
try:
look_for = "Version"
return agent.split(look_for)[1][1:].split(' ')[0]
except IndexError:
look_for = "Opera"
version = agent.split(look_for)[1][1:].split(' ')[0]
return version.split('(')[0]
class OperaNew(Browser):
"""
Opera after version 15
"""
name = "Opera"
look_for = "OPR"
version_markers = [('/', '')]
class Netscape(Browser):
look_for = "Netscape"
version_markers = [("/", '')]
class Trident(Browser):
look_for = "Trident"
skip_if_found = ["MSIE", "Opera"]
name = "Microsoft Internet Explorer"
version_markers = ["/", ";"]
trident_to_ie_versions = {
'4.0': '8.0',
'5.0': '9.0',
'6.0': '10.0',
'7.0': '11.0',
}
def getVersion(self, agent, word):
return self.trident_to_ie_versions.get(super(Trident, self).getVersion(agent, word))
class MSIE(Browser):
look_for = "MSIE"
skip_if_found = ["Opera"]
name = "Microsoft Internet Explorer"
version_markers = [" ", ";"]
class MSEdge(Browser):
look_for = "Edge"
skip_if_found = ["MSIE"]
version_markers = ["/", ""]
class Galeon(Browser):
look_for = "Galeon"
class WOSBrowser(Browser):
look_for = "wOSBrowser"
def getVersion(self, agent, word):
pass
class Safari(Browser):
look_for = "Safari"
skip_if_found = ["Edge"]
def checkWords(self, agent):
unless_list = ["Chrome", "OmniWeb", "wOSBrowser", "Android"]
if self.look_for in agent:
for word in unless_list:
if word in agent:
return False
return self.look_for
def getVersion(self, agent, word):
if "Version/" in agent:
return agent.split('Version/')[-1].split(' ')[0].strip()
if "Safari/" in agent:
return agent.split('Safari/')[-1].split(' ')[0].strip()
else:
return agent.split('Safari ')[-1].split(' ')[0].strip() # Mobile Safari
class GoogleBot(Browser):
# https://support.google.com/webmasters/answer/1061943
look_for = ["Googlebot", "Googlebot-News", "Googlebot-Image",
"Googlebot-Video", "Googlebot-Mobile", "Mediapartners-Google",
"Mediapartners", "AdsBot-Google", "web/snippet"]
bot = True
version_markers = [('/', ';'), ('/', ' ')]
class GoogleFeedFetcher(Browser):
look_for = "Feedfetcher-Google"
bot = True
def get_version(self, agent):
pass
class RunscopeRadar(Browser):
look_for = "runscope-radar"
bot = True
class GoogleAppEngine(Browser):
look_for = "AppEngine-Google"
bot = True
def get_version(self, agent):
pass
class GoogleApps(Browser):
look_for = "GoogleApps script"
bot = True
def get_version(self, agent):
pass
class TwitterBot(Browser):
look_for = "Twitterbot"
bot = True
class MJ12Bot(Browser):
look_for = "MJ12bot"
bot = True
class YandexBot(Browser):
# http://help.yandex.com/search/robots/agent.xml
look_for = "Yandex"
bot = True
def getVersion(self, agent, word):
return agent[agent.index('Yandex'):].split('/')[-1].split(')')[0].strip()
class BingBot(Browser):
look_for = "bingbot"
version_markers = ["/", ";"]
bot = True
class BaiduBot(Browser):
# http://help.baidu.com/question?prod_en=master&class=1&id=1000973
look_for = ["Baiduspider", "Baiduspider-image", "Baiduspider-video",
"Baiduspider-news", "Baiduspider-favo", "Baiduspider-cpro",
"Baiduspider-ads"]
bot = True
version_markers = ('/', ';')
class LinkedInBot(Browser):
look_for = "LinkedInBot"
bot = True
class ArchiveDotOrgBot(Browser):
look_for = "archive.org_bot"
bot = True
class YoudaoBot(Browser):
look_for = "YoudaoBot"
bot = True
class YoudaoBotImage(Browser):
look_for = "YodaoBot-Image"
bot = True
class RogerBot(Browser):
look_for = "rogerbot"
bot = True
class TweetmemeBot(Browser):
look_for = "TweetmemeBot"
bot = True
class WebshotBot(Browser):
look_for = "WebshotBot"
bot = True
class SensikaBot(Browser):
look_for = "SensikaBot"
bot = True
class YesupBot(Browser):
look_for = "YesupBot"
bot = True
class DotBot(Browser):
look_for = "DotBot"
bot = True
class PhantomJS(Browser):
look_for = "Browser/Phantom"
bot = True
class FacebookExternalHit(Browser):
look_for = 'facebookexternalhit'
bot = True
class NokiaOvi(Browser):
look_for = "S40OviBrowser"
class UCBrowser(Browser):
look_for = "UCBrowser"
class BrowserNG(Browser):
look_for = "BrowserNG"
class Dolfin(Browser):
look_for = 'Dolfin'
class NetFront(Browser):
look_for = 'NetFront'
class Jasmine(Browser):
look_for = 'Jasmine'
class Openwave(Browser):
look_for = 'Openwave'
class UPBrowser(Browser):
look_for = 'UP.Browser'
class OneBrowser(Browser):
look_for = 'OneBrowser'
class ObigoInternetBrowser(Browser):
look_for = 'ObigoInternetBrowser'
class TelecaBrowser(Browser):
look_for = 'TelecaBrowser'
class MAUI(Browser):
look_for = 'Browser/MAUI'
def getVersion(self, agent, word):
version = agent.split("Release/")[-1][:10]
return version
class NintendoBrowser(Browser):
look_for = 'NintendoBrowser'
class AndroidBrowser(Browser):
look_for = "Android"
skip_if_found = ['Chrome', 'Windows Phone']
# http://decadecity.net/blog/2013/11/21/android-browser-versions
def getVersion(self, agent, word):
pass
class Linux(OS):
look_for = 'Linux'
platform = 'Linux'
def getVersion(self, agent, word):
pass
class Blackberry(OS):
look_for = 'BlackBerry'
platform = 'BlackBerry'
def getVersion(self, agent, word):
pass
class BlackberryPlaybook(Dist):
look_for = 'PlayBook'
platform = 'BlackBerry'
def getVersion(self, agent, word):
pass
class WindowsPhone(OS):
name = "Windows Phone"
platform = 'Windows'
look_for = ["Windows Phone OS", "Windows Phone"]
version_markers = [(" ", ";"), (" ", ")")]
class iOS(OS):
look_for = ('iPhone', 'iPad')
skip_if_found = ['like iPhone']
class iPhone(Dist):
look_for = 'iPhone'
platform = 'iOS'
skip_if_found = ['like iPhone']
def getVersion(self, agent, word):
version_end_chars = [' ']
if not "iPhone OS" in agent:
return None
part = agent.split('iPhone OS')[-1].strip()
for c in version_end_chars:
if c in part:
version = part.split(c)[0]
return version.replace('_', '.')
return None
class IPad(Dist):
look_for = 'iPad;'
platform = 'iOS'
def getVersion(self, agent, word):
version_end_chars = [' ']
if not "CPU OS " in agent:
return None
part = agent.split('CPU OS ')[-1].strip()
for c in version_end_chars:
if c in part:
version = part.split(c)[0]
return version.replace('_', '.')
return None
class Macintosh(OS):
look_for = 'Macintosh'
def getVersion(self, agent, word):
pass
class MacOS(Flavor):
look_for = 'Mac OS'
platform = 'Mac OS'
skip_if_found = ['iPhone', 'iPad']
def getVersion(self, agent, word):
version_end_chars = [';', ')']
part = agent.split('Mac OS')[-1].strip()
for c in version_end_chars:
if c in part:
version = part.split(c)[0]
return version.replace('_', '.')
return ''
class Windows(Dist):
look_for = 'Windows'
platform = 'Windows'
class Windows(OS):
look_for = 'Windows'
platform = 'Windows'
skip_if_found = ["Windows Phone"]
win_versions = {
"NT 10.0": "10",
"NT 6.3": "8.1",
"NT 6.2": "8",
"NT 6.1": "7",
"NT 6.0": "Vista",
"NT 5.2": "Server 2003 / XP x64",
"NT 5.1": "XP",
"NT 5.01": "2000 SP1",
"NT 5.0": "2000",
"98; Win 9x 4.90": "Me"
}
def getVersion(self, agent, word):
v = agent.split('Windows')[-1].split(';')[0].strip()
if ')' in v:
v = v.split(')')[0]
v = self.win_versions.get(v, v)
return v
class Ubuntu(Dist):
look_for = 'Ubuntu'
version_markers = ["/", " "]
class Debian(Dist):
look_for = 'Debian'
version_markers = ["/", " "]
class Chrome(Browser):
look_for = "Chrome"
version_markers = ["/", " "]
skip_if_found = ["OPR", "Edge"]
def getVersion(self, agent, word):
part = agent.split(word + self.version_markers[0])[-1]
version = part.split(self.version_markers[1])[0]
if '+' in version:
version = part.split('+')[0]
return version.strip()
class ChromeiOS(Browser):
look_for = "CriOS"
version_markers = ["/", " "]
class ChromeOS(OS):
look_for = "CrOS"
platform = ' ChromeOS'
version_markers = [" ", " "]
def getVersion(self, agent, word):
version_markers = self.version_markers
if word + '+' in agent:
version_markers = ['+', '+']
return agent.split(word + version_markers[0])[-1].split(version_markers[1])[1].strip()[:-1]
class Android(Dist):
look_for = 'Android'
platform = 'Android'
skip_if_found = ['Windows Phone']
def getVersion(self, agent, word):
return agent.split(word)[-1].split(';')[0].strip()
class WebOS(Dist):
look_for = 'hpwOS'
def getVersion(self, agent, word):
return agent.split('hpwOS/')[-1].split(';')[0].strip()
class NokiaS40(OS):
look_for = 'Series40'
platform = 'Nokia S40'
def getVersion(self, agent, word):
pass
class Symbian(OS):
look_for = ['Symbian', 'SymbianOS']
platform = 'Symbian'
class PlayStation(OS):
look_for = ['PlayStation', 'PLAYSTATION']
platform = 'PlayStation'
version_markers = [" ", ")"]
class prefs: # experimental
os = dict(
Linux=dict(dict(browser=[Firefox, Chrome], dist=[Ubuntu, Android])),
BlackBerry=dict(dist=[BlackberryPlaybook]),
Macintosh=dict(flavor=[MacOS]),
Windows=dict(browser=[MSIE, Firefox]),
ChromeOS=dict(browser=[Chrome]),
Debian=dict(browser=[Firefox])
)
dist = dict(
Ubuntu=dict(browser=[Firefox]),
Android=dict(browser=[Safari]),
IPhone=dict(browser=[Safari]),
IPad=dict(browser=[Safari]),
)
flavor = dict(
MacOS=dict(browser=[Opera, Chrome, Firefox, MSIE])
)
detectorshub = DetectorsHub()
def detect(agent, fill_none=False):
"""
fill_none: if name/version is not detected respective key is still added to the result with value None
"""
result = dict(platform=dict(name=None, version=None))
_suggested_detectors = []
for info_type in detectorshub:
detectors = _suggested_detectors or detectorshub[info_type]
for detector in detectors:
try:
detector.detect(agent, result)
except Exception as _err:
pass
if fill_none:
attrs_d = {'name': None, 'version': None}
for key in ('os', 'browser'):
if key not in result:
result[key] = attrs_d
else:
for k, v in attrs_d.items():
result[k] = v
return result
def simple_detect(agent):
"""
-> (os, browser) # tuple of strings
"""
result = detect(agent)
os_list = []
if 'flavor' in result:
os_list.append(result['flavor']['name'])
if 'dist' in result:
os_list.append(result['dist']['name'])
if 'os' in result:
os_list.append(result['os']['name'])
os = os_list and " ".join(os_list) or "Unknown OS"
os_version = os_list and (result.get('flavor') and result['flavor'].get('version')) or \
(result.get('dist') and result['dist'].get('version')) or (result.get('os') and result['os'].get('version')) or ""
browser = 'browser' in result and result['browser'].get('name') or 'Unknown Browser'
browser_version = 'browser' in result and result['browser'].get('version') or ""
if browser_version:
browser = " ".join((browser, browser_version))
if os_version:
os = " ".join((os, os_version))
return os, browser

View file

@ -0,0 +1,29 @@
import httpagentparser as hap
class JakartaHTTPClinet(hap.Browser):
name = 'Jakarta Commons-HttpClient'
look_for = name
version_splitters = ['/']
class PythonRequests(hap.Browser):
name = 'Python Requests'
look_for = 'python-requests'
# Registering new UAs
hap.detectorshub.register(JakartaHTTPClinet())
hap.detectorshub.register(PythonRequests())
# Tests
if __name__ == '__main__':
s = 'Jakarta Commons-HttpClient/3.1'
print(hap.detect(s))
print(hap.simple_detect(s))
s = 'python-requests/1.2.3 CPython/2.7.4 Linux/3.8.0-29-generic'
print(hap.detect(s))
print(hap.simple_detect(s))

View file

@ -473,6 +473,12 @@ def dbcheck():
'deleted_section INTEGER DEFAULT 0, UNIQUE(server_id, section_id))'
)
# user_login table :: This table keeps record of the PlexPy guest logins
c_db.execute(
'CREATE TABLE IF NOT EXISTS user_login (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'timestamp INTEGER, user_id INTEGER, user TEXT, user_group TEXT, ip_address TEXT, host TEXT, user_agent TEXT)'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT started FROM sessions')

View file

@ -13,6 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
import httpagentparser
import time
import plexpy
import common
import database
@ -671,3 +674,87 @@ class Users(object):
filters_list[k] = filters
return filters_list
def set_user_login(self, user_id=None, user=None, user_group=None, ip_address=None, host=None, user_agent=None):
if user_id is None or str(user_id).isdigit():
monitor_db = database.MonitorDatabase()
keys = {'timestamp': int(time.time()),
'user_id': user_id}
values = {'user': user,
'user_group': user_group,
'ip_address': ip_address,
'host': host,
'user_agent': user_agent}
try:
monitor_db.upsert(table_name='user_login', key_dict=keys, value_dict=values)
except Exception as e:
logger.warn(u"PlexPy Users :: Unable to execute database query for set_login_log: %s." % e)
def get_datatables_user_login(self, user_id=None, kwargs=None):
default_return = {'recordsFiltered': 0,
'recordsTotal': 0,
'draw': 0,
'data': 'null',
'error': 'Unable to execute database query.'}
if not session.allow_session_user(user_id):
return default_return
data_tables = datatables.DataTables()
custom_where = [['user_id', user_id]]
columns = ['user_login.user_id',
'user_login.user',
'user_login.user_group',
'user_login.ip_address',
'user_login.host',
'user_login.user_agent',
'user_login.timestamp',
'users.friendly_name'
]
try:
query = data_tables.ssp_query(table_name='user_login',
columns=columns,
custom_where=custom_where,
group_by=[],
join_types=['LEFT OUTER JOIN'],
join_tables=['users'],
join_evals=[['user_login.user_id', 'users.user_id']],
kwargs=kwargs)
except Exception as e:
logger.warn(u"PlexPy Users :: Unable to execute database query for get_datatables_user_login: %s." % e)
return default_return
results = query['result']
rows = []
for item in results:
(os, browser) = httpagentparser.simple_detect(item['user_agent'])
row = {'user_id': item['user_id'],
'user': item['user'],
'user_group': item['user_group'],
'ip_address': item['ip_address'],
'host': item['host'],
'user_agent': item['user_agent'],
'os': os,
'browser': browser,
'timestamp': item['timestamp'],
'friendly_name': item['friendly_name']
}
rows.append(row)
dict = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': session.friendly_name_to_username(rows),
'draw': query['draw']
}
return dict

View file

@ -178,13 +178,26 @@ def all_of(*conditions):
class AuthController(object):
def on_login(self, username):
def on_login(self, user_id, username, user_group):
"""Called on successful login"""
logger.debug(u"User '%s' logged into PlexPy." % username)
def on_logout(self, username):
# Save login to the database
ip_address = cherrypy.request.headers.get('X-Forwarded-For', cherrypy.request.headers.get('Remote-Addr'))
host = cherrypy.request.headers.get('Origin')
user_agent = cherrypy.request.headers.get('User-Agent')
Users().set_user_login(user_id=user_id,
user=username,
user_group=user_group,
ip_address=ip_address,
host=host,
user_agent=user_agent)
logger.debug(u"%s user '%s' logged into PlexPy." % (user_group.capitalize(), username))
def on_logout(self, username, user_group):
"""Called on logout"""
logger.debug(u"User '%s' logged out of PlexPy." % username)
logger.debug(u"%s User '%s' logged out of PlexPy." % (user_group.capitalize(), username))
def get_loginform(self, username="", msg=""):
from plexpy.webserve import serve_template
@ -224,7 +237,7 @@ class AuthController(object):
'user_group': user_group,
'expiry': expiry}
self.on_login(username)
self.on_login(user_id, username, user_group)
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
elif admin_login == '1':
@ -244,5 +257,5 @@ class AuthController(object):
if _session and _session['user']:
cherrypy.request.login = None
self.on_logout(_session['user'])
self.on_logout(_session['user'], _session['user_group'])
raise cherrypy.HTTPRedirect("login")

View file

@ -1165,6 +1165,27 @@ class WebInterface(object):
return history
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth()
def get_user_logins(self, user_id=None, **kwargs):
""" Get the data on PlexPy user login table. """
# Check if datatables json_data was received.
# If not, then build the minimal amount of json data for a query
if not kwargs.get('json_data'):
# TODO: Find some one way to automatically get the columns
dt_columns = [("time", True, False),
("ip_address", True, True),
("host", True, True),
("os", True, True),
("browser", True, True)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "time")
user_data = users.Users()
history = user_data.get_datatables_user_login(user_id=user_id, kwargs=kwargs)
return history
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))