forked from hjone72/LDAP-for-Plex
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLDAPPlex.js
290 lines (256 loc) · 8.75 KB
/
LDAPPlex.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// Imports
var request = require('request');
var uuid = require('uuid');
var parseString = require('xml2js').parseString;
var ldap = require('ldapjs');
var crypto = require('crypto');
var fs = require('fs');
const defaults = {
debug: false,
port: 2389,
host: '0.0.0.0',
rootDN: 'ou=users, o=plex.tv',
plexToken: '',
plexMachineID: '',
plexServerName: ''
};
const optionsFile = 'config/options.json';
const configFolder = 'config/'
if(!fs.existsSync(optionsFile) && !fs.existsSync(configFolder)) {
fs.mkdirSync(configFolder);
var json = JSON.stringify(defaults, null, '\t');
fs.writeFileSync(optionsFile, json);
console.log("Please fill out config/options.json");
return;
} else if (!fs.existsSync(optionsFile)) {
var json = JSON.stringify(defaults, null, '\t');
fs.writeFileSync(optionsFile, json);
console.log("Please fill out config/options.json");
return;
}
var config = require('./config/options.json');
// Configuration
var version = '0.2';
var debug = config.debug;
var ldapPort = config.port;
var ldapHostname = config.host;
var rootDN = config.rootDN; // This can be anything you like. It won't change anything though.
var plexToken = config.plexToken; // Your Plex token. This is used to get your friends list.
var plexMachineID = config.plexMachineID; // Only allow servers that have this MachineID.
var plexServerName = config.plexServerName; // The name of your server.
// Variables
var plexUser;
var server = ldap.createServer();
const headers = {
'X-Plex-Client-Identifier': uuid.v4(),
'X-Plex-Product': 'LDAP for Plex',
'X-Plex-Device': 'LDAP for Plex',
'X-Plex-Version': 'v' + version,
'content-Type': 'application/xml; charset=utf-8',
'Content-Length': 0
};
var options = {
url: 'https://plex.tv/users/sign_in.json',
method: 'POST',
headers: headers
};
var db = {}; // In memory database. This also acts as a cache.
// Functions
function authHeaderVal(username, password) {
// Generate a value based on UN and PW to send with the header for authentication.
var authString = username + ':' + password;
var buffer = new Buffer(authString.toString(), 'binary');
return 'Basic ' + buffer.toString('base64');
}
/*
function hashServer(server) {
// <Server name="!PlexyName!" machineIdentifier="abcd" createdAt="1234"/>
var toMD5 = server.name + server.machineIdentifier + server.createdAt;
var serverHash = crypto.createHash('md5').update(toMD5).digest("hex");
serverDB[serverHash] = server.name;
}
*/
function log(msg) {
if (debug) {
console.log(msg);
}
}
function loadPlexUser(username, password) {
var loginHeaders = headers;
loginHeaders.Authorization = authHeaderVal(username, password);
var loginOptions = options;
loginOptions.headers = loginHeaders;
return new Promise(function (resolve, reject) {
request(loginOptions, function (err, res, body) {
if (!err && (res.statusCode == 200 || res.statusCode == 201)) {
plexUser = (JSON.parse(body).user);
plexUserToLDAP(plexUser);
return resolve(plexUser);
} else {
return reject(body);
}
});
});
}
function plexUserToLDAP(pUser, servers) {
var obj = {
attributes: {
objectclass: ['Plex.tv User'],
cn: pUser.username,
uid: pUser.id,
email: pUser.email,
title: pUser.title,
thumb: pUser.thumb,
o: 'plex.tv'
}
};
if (servers) {
servers.forEach(function (server) {
if (plexMachineID == server.$.machineIdentifier) {
obj.attributes.groups = [server.$.name];
}
});
}
db['uid=' + pUser.id + ', ' + rootDN] = obj;
}
function loadPlexUsers(token) {
return new Promise(function (resolve, reject) {
var loadMe = function (callback) {
request('https://plex.tv/users/account?X-Plex-Token=' + token, function (err, res, body) {
// Load in the current user. You don't appear in your own friends list.
if (!err && res.statusCode == 200) {
parseString(body, function (err, result) {
var me = result.user.$;
me.username = result.user.username;
var server = {$: {machineIdentifier: plexMachineID, name: plexServerName}}; // You don't appear in your friends list. Build some information so that you can auth too.
plexUserToLDAP(me, [server]);
});
} else {
log(body);
return reject();
}
});
};
request('https://plex.tv/api/users?X-Plex-Token=' + token, function (err, res, body) {
if (!err && res.statusCode == 200) {
parseString(body, function (err, result) {
var users = result.MediaContainer.User;
users.forEach(function (user) {
plexUserToLDAP(user.$, user.Server);
});
return loadMe(resolve());
});
} else {
log(body);
return reject();
}
});
});
}
// Start //
// LDAP Server //
if (plexToken === '') {
console.log('A valid Plex token is required...');
process.exit();
} else {
// Preload database.
console.log('Preloading Plex users...');
loadPlexUsers(plexToken)
.then(function () {
console.log('Database loaded.');
server.listen(ldapPort, ldapHostname, function () {
console.log('LDAP for Plex server up at: %s', server.url);
});
})
.catch();
}
server.bind(rootDN, function (req, res, next) {
log('bindDN: ' + req.dn.toString());
if (db[req.dn.toString()]) {
var username = db[req.dn.toString()].attributes.cn;
} else {
return next(new ldap.NoSuchObjectError(dn));
}
loadPlexUser(username, req.credentials)
.then(function (user) {
res.end();
return next();
})
.catch(function (err) {
console.log(err);
return next(new ldap.InvalidCredentialsError());
});
});
server.search(rootDN, function (req, res, next) {
log('base object: ' + req.dn.toString());
log('scope: ' + req.scope);
log('filter: ' + req.filter.toString());
var dn = req.dn.toString();
var scopeCheck;
var filled = false;
var search = function (req, res, next) {
switch (req.scope) {
case 'base':
if (rootDN !== dn) {
if (!db[dn]) {
return next(new ldap.NoSuchObjectError(dn));
}
if (req.filter.matches(db[dn].attributes)) {
filled = true;
res.send({
dn: dn,
attributes: db[dn].attributes
});
}
res.end();
return next();
}
case 'one':
scopeCheck = function (k) {
if (req.dn.equals(k)) {
return true;
}
var parent = ldap.parseDN(k).parent();
return (parent ? parent.equals(req.dn) : false);
};
if (req.filter.toString() == '(objectclass=*)' && req.dn.toString() !== rootDN) {
res.end();
return next();
}
break;
case 'sub':
scopeCheck = function (k) {
return (req.dn.equals(k) || req.dn.parentOf(k));
};
break;
}
Object.keys(db).forEach(function (key) {
if (!scopeCheck(key)) {
log('Skipping this key as scopeCheck returned false. ' + key);
return;
}
if (req.filter.matches(db[key].attributes)) {
filled = true;
res.send({
dn: 'uid=' + db[key].attributes.uid + ', ' + rootDN,
attributes: db[key].attributes
});
}
});
if (filled) {
log('request is reported as filled.');
res.end();
return next();
}
};
search(req, res, next);
if (!filled) {
// Load database again. There may have been changes.
loadPlexUsers(plexToken)
.then(function () {
log('Database reloaded.');
filled = true;
search(req, res, next);
})
}
});