mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-14 01:02:59 -07:00
Log PlexPy logins to database
This commit is contained in:
parent
27716d080f
commit
8ed7688277
8 changed files with 1018 additions and 7 deletions
127
data/interfaces/default/js/tables/login_table.js
Normal file
127
data/interfaces/default/js/tables/login_table.js
Normal 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> Fetching 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']);
|
||||
});
|
|
@ -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() {
|
||||
|
|
675
lib/httpagentparser/__init__.py
Normal file
675
lib/httpagentparser/__init__.py
Normal 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
|
29
lib/httpagentparser/more.py
Normal file
29
lib/httpagentparser/more.py
Normal 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))
|
|
@ -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')
|
||||
|
|
|
@ -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
|
|
@ -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")
|
|
@ -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"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue