• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

    На последнюю неделю приходится экзамен, где нужно будет показать свои навыки, взломав ряд уязвимых учебных сайтов, и добыть флаги. Успешно сдавшие экзамен получат сертификат.

    Запись на курс до 25 апреля. Получить промодоступ ...

Lotus Domino + Document Server ONLYOFFICE

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
Дорогие коллеги,
Хочу рассказать вам о небольшом эксперименте/опыте
Интеграция с ONLYOFFCE
Зачем это надо:
-скачивать документ на локал не всегда хочется, да и на моюбильных устройствах могут быть траблы, под документом я подразумеваю аттач типа: docx|xlsx|pptx... далее по списку
-установка приложения и прокачка по сети - тоже не вариант, бывает

Особенности:
-расскажу только про чтение, у редактирования могут быть особенности с размещением потока на домину
-должен быть развернут и настроен отмечу, что необходим внешний доступ к этому серверу по https!
-в свете текущего направления браузеров http (без s) вне закона, поэтому - домина тоже на https
-пример будет для xPage, хотя и для "классического" веб интерфейса домины все аналогично

используем базовый вариант API без редактирования, вариант для xlsx (важно - documentType):
JavaScript:
var docEditor = new DocsAPI.DocEditor(placeholder, {
    "document": {
        "fileType": "xlsx",
        "key": key,
        "title": title,
        "url": url
    },
    "token": token,
    "documentType": "spreadsheet"
});
если documentType будет ошибочный - можем получить сильно свопирующую систему (браузер начнет нещадно жрать память)! Возможно починят (JS), но не факт ;)
по остальным параметрам:
- fileType -- пес его знает нужен ли вовсе
- key - закономерность я уловил только отчасти, он не должен повторяться, если документы разные (кэширование, серверром). Как в случае изменения дока - не знаю, возможно нужно менять
- title - что в голову взбредет
- url - тот конечный урл, с кот. может быть скачан файл
- token - очень интересный момент, при установке docserver там будет дефолтный (т.е. не будет вовсе), но позволять к-л напрягать сервер попусту - это не вариант ;) придумываем слово (ключ) и записываем его в конфигурацию docserver, прописывается От прописанного ключа token, в соответ. поле
1521735477404.png

результат - в переменную (коряво, могут подсмотреть враги, в отладчике, ашоделать)

В домине придется открывать док для публичного просмотра - $PublicAccess="1" и поле ридер с Anonymous (морочиться с аторизацией docserver на домине - не вариант), учитывая что док м.б. доступен по ID/индексу поиска - можно подумать на тему временной ссылки. Я сделал (в макете) влоб - по UNID
теперь шаблон xPages:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.resources>

        <xp:script
            src="https://docserver/web-apps/apps/api/documents/api.js"
            clientSide="true">
        </xp:script>
    </xp:this.resources>
    <xp:panel id="placeholder"></xp:panel>
    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[var serverURI="#{javascript:var url = context.getUrl();return url.getScheme() + '://' + url.getHost();}";
var filePath="/path2DB/DBname.nsf/xsp/.ibmmodres/domino/OpenAttachment/templates/DBname.nsf/UNID/Body/fileNameWithEscapeNonASCII.docx";
//unid as key
var url=serverURI+"/path2DB/DBname.nsf/0/UNID/$FILE/fileNameWithEscapeNonASCII.docx";
var key="UNID+sid";
//filename as title
var title="Some Doc";
var placeholder="#{id:placeholder}";
//http://jwtbuilder.jamiekurtz.com/ create token from your secret key
var token="here we have token has been genarated previously";
var docEditor = new DocsAPI.DocEditor(placeholder, {
    "document": {
        "fileType": "docx",
        "key": key,
        "title": title,
        "url": url
    },
    "token": token,
    "documentType": "text"
});
]]></xp:this.value>
    </xp:scriptBlock></xp:view>
в примере url.getScheme() подразумевает что домина по https ! иначе будет странное поведение из-за смешивания https и http, попросту - не будет работать
ключ можно генерить из UNID + время (если аттач меняться не будет - время не нужно)
 
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
сделал параметризованный вызов xsp (параметр id=UNID), убрал хардкоденные части (исключая token):
XML:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.resources>

        <xp:script
            src="https://docserver/web-apps/apps/api/documents/api.js"
            clientSide="true">
        </xp:script>
    </xp:this.resources>
    <xp:panel id="placeholder"></xp:panel>
    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[var serverURI="#{javascript:var url = context.getUrl();return url.getScheme() + '://' + url.getHost();}";
    var host="#{javascript:return url.getHost();}";
var dbURL = "#{javascript:var httpURL = database.getHttpURL();return httpURL}".replace("http://", "https://").split("?", 1)[0];
//unid as key
var unid="#{javascript:return context.getUrlParameter('id');}";
var att = "#{javascript:var docId = context.getUrlParameter('id');var att=session.evaluate('@AttachmentNames', database.getDocumentByUNID(docId));return att[0];}";
var url=dbURL+"/0/"+unid+"/$FILE/"+encodeURI(att);

var sid="";
var key=unid+sid;

var documentType="";
var ext=att.split(".").pop();
switch (ext)
{
  case "doc":
  case "docm":
  case "docx":
  case "dot":
  case "dotm":
  case "dotx":
  case "epub":
  case "fodt":
  case "htm":
  case "html":
  case "mht":
  case "odt":
  case "pdf":
  case "rtf":
  case "txt":
  case "djvu":
  case "xps":
    documentType="text";
    break;
  case "csv":
  case "fods":
  case "ods":
  case "xls":
  case "xlsm":
  case "xlsx":
  case "xlt":
  case "xltm":
  case "xltx":
    documentType="spreadsheet";
    break;
  case "fodp":
  case "odp":
  case "pot":
  case "potm":
  case "potx":
  case "pps":
  case "ppsm":
  case "ppsx":
  case "ppt":
  case "pptm":
  case "pptx":
    documentType="presentation";
    break;
  default:
       alert('Bad file type');
       exit;
}
//filename as title
var title=att;

//http://jwtbuilder.jamiekurtz.com/ create token from your secret key
var token="here we have token has been genarated previously";
var docEditor = new DocsAPI.DocEditor(placeholder, {

    "document": {
        "fileType": ext,
        "key": key,
        "title": title,
        "url": url
    },
    "token": token,
    "documentType": documentType
});

]]></xp:this.value>
    </xp:scriptBlock></xp:view>
 
  • Нравится
Реакции: Vertigo, yarr и VladSh

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
возможно из моих постов не было очевидным, но токен м.б . любой (я отправляю к-то фигню на основе ключа), это для случая чтения
сильно подозреваю - для сохранения/конвертации и т.п. понадобится валидный (т.е. с учетом передаваемого JSON)
можно коротко прочитать
над темой как подписывать JSON динамически я не думал
вот концепт , пихать в payload надо config ( , инфа скудная), но без самого токена, токен добавить как config.token="тут ложим токен :)";
генерация токена может пригодится на этапе удаления доков из БД докуметсервера (а он их там хранит, по ключу), через
 
Последнее редактирование:
  • Нравится
Реакции: Vertigo

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
очередные изыскания и неудачные попытки получить валидный хэш привели к
я пытался получать отдельно (без гугл либ) base64, но чего-то там не хватало, плюнул...
полный xsp причесывать лень, выложу значимые куски
доп. либы:
XML:
<xp:script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/core.js" clientSide="true"></xp:script>
<xp:script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/enc-base64.js" clientSide="true"></xp:script>
<xp:script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/hmac-sha256.js" clientSide="true"></xp:script>
цельно пи.. взятый код
JavaScript:
var config={
    "document": {
        "fileType": ext,
        "key": key,
        "title": title,
        "url": url
    },
    "documentType": documentType
};

const header = {"typ":"JWT","alg":"HS256"};
const data=config;
const secret= 'my super secret';

/*заимствованный кусок*/
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);

var token = encodedHeader + "." + encodedData;

var signature = CryptoJS.HmacSHA256(token, secret);
signature = base64url(signature);

var signedToken = token + "." + signature;
/*заимствованный кусок закончился*/
config.token=signedToken;
var docEditor = new DocsAPI.DocEditor(placeholder, config);
как видно - кофиг я просто "вынес", а затем - добавил токен
т.о. теперь можно генерить токен, разумеется это нужно делать на сервере, я код сделал для клиента
Вынос кода на сервер сопряжен с переносом кода по ссылкам (cdnjs.cloudflare.com) на серверную часть
насколько я понимаю - либы самодостаточные, но комплект д.б. именно такой
ссылки на либы нашел через стековерфло, они здесь
более свежую версию подключить не получилось, они (версии свежее) заточены под новомодные ноды, адаптировать/искать комбинации было лень
 
Последнее редактирование:

Domino-Designer

Людям надо поморгать!
Lotus Team
06.12.2011
616
223
BIT
9
Зачем это надо:
-скачивать документ на локал не всегда хочется, да и на моюбильных устройствах могут быть траблы, под документом я подразумеваю аттач типа: docx|xlsx|pptx... далее по списку
-установка приложения и прокачка по сети - тоже не вариант, бывает


з-з-ачем?
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
написано - не скачивать на локальный комп документ, для просмотра (а в последствии - редактирования), не устанавливать всякие МСО, для чтения дока (перечисленных форматов), все будет в браузере
 

Domino-Designer

Людям надо поморгать!
Lotus Team
06.12.2011
616
223
BIT
9
вы предлагаете скачивать его в другую сторону
всёравно скачивать, да?
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
вы предлагаете скачивать его в другую сторону
всёравно скачивать, да?
в какую другую? на сервер во внутренней сети - это менее накладно ;)
Представим:
- "плохой" канал (3Ж) - самый раз такой подход
- нет МСО и прочих офисных программ (анах они нужны везде)
- сервер-сервер связь с клиент-сервер сравнивать... ну сами понимаете, добавим - это м.б. контейнер (у меня он на др. ноде, но все же). Добавим и/или 10Гбит
- совместное редактирование...
 
  • Нравится
Реакции: Domino-Designer

duchan

Green Team
20.09.2006
127
11
BIT
111

Например, что бы создать самодостаточное приложение, которому нет необходимости во внешних (клиентских) зависимостях (MCO, Windows, PC only)
Сам давно поглядывал в сторону OnlyOffice, но ни как руки не дойдут выкроить время на эксперименты. Если еще решить вопрос с редактированием и сохранением аттачей, то это готовое интеграционное решение.
Это как в своё время, начали через COM в ворде из лотуса работать с документами. Многие не понимали "зачем", но в тот момент это было пожалуй лучшее решение по работе\созданию конечных документов, а не тупо РКК. Работа с конечным документом и позволила от простейших картотечных систем делопроизводства, перейти к системам документооборота.
Так и здесь, если решить проблемы и развить направление, то можно будет создавать нормальные системы документооборота построеные только на браузере (на клиенте). Так что Imike - жги! ;) "Золотой бубен" тебе в помощь! ;) ;) :)
 
  • Нравится
Реакции: Domino-Designer

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
еще кучка граблей ;), на пути перевода на серверную генерацию хэша...
я пытался ограничиться JavaScript, но, судя по процессу - все усложнилось ;)
суть:
1. надо генерить хэш на сервере по сесуриту (security)
2. доминошный движок не умеет забирать внешние либы
3. либы от гугла - "клиентские" (они используют ф-ции зашитые в современные браузеры)
Т.о. - подключить или перенести гугллибы на серверную сторону крайне сложно (я уж точно - переписывать и раскапывать не буду). Нашел интересный код он пригодился бы, еслиб бы не грабли номер 3. ;)
эти же грабли не позволили мне перенести код по ссылкам в SSJS, как минимум - не хватило encodeURIComponent
замена на не решира траблу (видать формат не тот или чего-то не хватило еще)
т.е. остался путь через java -
[automerge]1522229346[/automerge]
ОФФ там домина ;)
[automerge]1522230455[/automerge]
редактированием и сохранением аттачей
это скорее вопрос подхода - как его лучше организовать
проблемы, как таковой, нет:
- документсервер уже хранит доки, кот. ему отдавали на рендеринг
- документ сервер сохраняет все изменения, сразу, у себя
- есть ф-ция конвертации - калбек, с возвратом сылки на документсервер

есть мое непонимание - как быть с аутентификацией, формально - угадать юнид сложнее чем пароль пользователя ;), но открывать анонимный доступ на запись не хочется, а передача аутентификационных cookie, onlyoffice, для возврата домине. мне не известна
на форуме (onlyoffice) видел упоминание о похотелках, но ответ был - "когда-нибудь"
 
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
концепт по секьюрити:
Создаем на БД домины 2-а дока: ключ, соль
соль обновляется раз в сутки
При обращении к документсерверу от unid дока создаем хэш (с ключем), его оздаем от unid+соль, серверным кодом.
В url прикладываем ...?id=unid+hash, где ... - адрес специального ресурса (СР)
СР - это м.б. хэпага, кот. имеет админский доступ к БД (или агент, для классики)
СР проверяет параметр, если хэш получился - отдает аттач, по unid из параметра
Т.о. - не нужно открывать доки на чтение/редактирование анониму
 
  • Нравится
Реакции: Vertigo

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
еще по доступу, предполагается грести
запускать агент, на мой взгляд (для хэпейдж), будет более накладным
еще попалось прототипирование, куда сунуть, пока не придумал, но учитывая глобальность для сервера - очень интересная
добавлю , по факту - получаем проксирующую страницу страницу, под текущим юзером
готовый код для токена - SSJS
код от @rinsk Авторизация Xpages В Браузере Без Пары Логин\пароль
[automerge]1522688766[/automerge]
продолжаем разговор ;)
страница редайректа, должна быть PublicAccess (никакиз токенов там не вычисляется и т.п.)
XML:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.beforePageLoad><![CDATA[#{javascript:try {
var url = context.getUrl();
var host = url.getHost();
var domain=host.match(/\..*/)[0];
var response=facesContext.getExternalContext().getResponse();

var token=paramValues.get("token").toString();
var rUrl=paramValues.get("url").toString();

/*
var user=@UserName();

var unid="test unid";

var httpURL = database.getHttpURL();
var dbUrl= httpURL.replace("http://", "https://").match(/(.+)\?/)[1];
print("url:"+dbUrl);
var docId = unid;//context.getUrlParameter('id');
var att=session.evaluate('@AttachmentNames', database.getDocumentByUNID(docId))[0];
var target=dbUrl + "/0/"+unid+"/$FILE/"+att;
var generated=LtpaGenerator(user);
token=generated;
*/
var header="LtpaToken=" + token + "; domain=" + domain + "; path=/";
response.setHeader("Set-Cookie", header);
facesContext.getExternalContext().redirect(rUrl)

} catch(e) {
        _dump(e);
}
}]]></xp:this.beforePageLoad>
    <xp:this.resources>
        <xp:script src="/SSJSUtils.jss" clientSide="false"></xp:script>
    </xp:this.resources>
    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[
var header="#{javascript:return header}";
console.log("header:" + header);]]></xp:this.value>
    </xp:scriptBlock>

</xp:view>
закоменченный код - это были тестовые запуски, либа с java кодом и её пользование описано выше, я создал SSJS (со своим бж и поэтессами)
JavaScript:
var errAttr="error!->";
function LtpaGenerator(userDN){
  importPackage(lmike.org);
  try{
    var ltpa:LtpaGenerator1=new LtpaGenerator1();
    if (arguments.length > 1){
      ltpa.initByConfiguration(sessionAsSigner,arguments[1]);
    }else{
      ltpa.initByConfiguration(sessionAsSigner);
    }
    var token=ltpa.generateLtpaToken(userDN);
    return token;
  }catch(e){
    return errAttr+e.toString();
  }
}
вызов клиентской части
[automerge]1522689417[/automerge]
вызов клиентской части
опять движок глючит..., м.б завтра допишу
[automerge]1522689750[/automerge]
код клиентской либы:
JavaScript:
//https://habrahabr.ru/post/340146/
var errAttr="error!->";
var redirect="redirect.xsp"
var header = {"typ":"JWT","alg":"HS256"};
//http://jwtbuilder.jamiekurtz.com/
var payload={"iss":"Online JWT Builder","iat":1521715924,"exp":1553251924,"aud":"www.example.com","sub":"jrocket@example.com","GivenName":"Johnny","Surname":"Rocket","Email":"jrocket@example.com","Role":["Manager","Project Administrator"]};
var token="";
var secret= 'top secret';
var bad="BAD";

function createJWT(data){
  var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
  var encodedHeader = base64url(stringifiedHeader);
  var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
  var encodedData = base64url(stringifiedData);
 
  var token = encodedHeader + "." + encodedData;
 
  var signature = CryptoJS.HmacSHA256(token, secret);
  signature = base64url(signature);
 
  return token + "." + signature;
}
//https://developer.mozilla.org/ru/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
function b64EncodeUnicode(str) {
    // first we use encodeURIComponent to get percent-encoded UTF-8,
    // then we convert the percent encodings into raw bytes which
    // can be fed into btoa.
    return  btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
        }))}

function base64url_(source) {
  // Encode in classical base64
  encodedSource = b64EncodeUnicode(source);//CryptoJS.enc.Base64.stringify(source);

  // Remove padding equal characters
  encodedSource = encodedSource.replace(/=+$/, '');

  // Replace characters according to base64url specifications
  encodedSource = encodedSource.replace(/\+/g, '-');
  encodedSource = encodedSource.replace(/\//g, '_');

  return encodedSource;
}
//https://www.jonathan-petitcolas.com/2014/11/27/creating-json-web-token-in-javascript.html
function base64url(source) {
  // Encode in classical base64
  encodedSource = CryptoJS.enc.Base64.stringify(source);

  // Remove padding equal characters
  encodedSource = encodedSource.replace(/=+$/, '');

  // Replace characters according to base64url specifications
  encodedSource = encodedSource.replace(/\+/g, '-');
  encodedSource = encodedSource.replace(/\//g, '_');

  return encodedSource;
}
function docType(ext){
  switch (ext)
  {
  case "doc":
  case "docm":
  case "docx":
  case "dot":
  case "dotm":
  case "dotx":
  case "epub":
  case "fodt":
  case "htm":
  case "html":
  case "mht":
  case "odt":
  case "pdf":
  case "rtf":
  case "txt":
  case "djvu":
  case "xps":
    return "text";
  case "csv":
  case "fods":
  case "ods":
  case "xls":
  case "xlsm":
  case "xlsx":
  case "xlt":
  case "xltm":
  case "xltx":
    return "spreadsheet";
  case "fodp":
  case "odp":
  case "pot":
  case "potm":
  case "potx":
  case "pps":
  case "ppsm":
  case "ppsx":
  case "ppt":
  case "pptm":
  case "pptx":
    return "presentation";
  default:
    return bad;
  }
}

function readDoc(dbURL,token,unid,att, placeholder){
  var url=dbURL+"/0/"+unid+"/$FILE/"+encodeURIComponent(att);//Alphasyn_EP_150%280000074116%29.docx";
  url=dbURL+"/" + redirect + "?token=" + token + "&url=" + url;
  console.log("document url:"+url);
  //return;
  //batches a26e9c1b870c0e654325811600449655 cbdd4a1876c14fa54325815d00587e67 Aircol_SR_46%280000074122%29.docx";
  var ext=att.split(".").pop();
  var sid=new Date().valueOf();
  var key=unid+sid;
  //filename as title
  var title=att;
  var documentType=docType(ext);
  if (documentType === bad) return;
  var config={
      "document": {
          "fileType": ext,
          "key": key,
          "title": title,
          "url": url
      },
      "documentType": documentType
  };
 
 
  config.token=createJWT(config);
  console.log("token:" + config.token);
  var docEditor = new DocsAPI.DocEditor(placeholder, config);
}
пока генерим JWT на клиенте
[automerge]1522690034[/automerge]
Т.о. авторизовавшись и вызывая страницу ...oo_read.xsp?id=<здесь д.б. unid>
мы выполняем создания токена, для текущего юзера на сервере, с этим токеном будет входить доксервер
по адресу ...redirect.xsp?token=<сгенеренный токен>&url=<здесь сформированный урл>, причем - доступ к этому урл не будет анонимным!
[automerge]1522691489[/automerge]
чета не получилось...
браузер получает аттач, а доксервер нет...
буду думать сюда
 
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
настроил nginx ловить LtpaToken, наглядная разница между браузером и доксервером:
Код:
browser - - [03/Apr/2018:12:55:15 +0300] "GET /__dbRID.nsf/redirect.xsp?token=token&url=https://server/__dbRID.nsf/0/unid/$FILE/attName.docx HTTP/1.1" 302 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Falkon/3.0.99 Chrome/56.0.2924.122 Safari/537.36" "-" "-" "-" "-" "-" "-"  LtpaToken:"request token"
browser - - [03/Apr/2018:12:55:15 +0300] "GET /__dbRID.nsf/0/unid/%24FILE/attName.docx HTTP/1.1" 200 789961 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Falkon/3.0.99 Chrome/56.0.2924.122 Safari/537.36" "-" "-" "-" "-" "-" "-"  LtpaToken:"request token"

docserver - - [03/Apr/2018:12:55:45 +0300] "GET /__dbRID.nsf/redirect.xsp?token=token&url=https://server/__dbRID.nsf/0/unid/$FILE/attName.docx HTTP/1.1" 302 0 "-" "-" "-" "-" "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjI3NDkzNDYsImV4cCI6MTUyMjc0OTY0Nn0.T4jpZ0y92Kw4iwS__8hQNgYjFXjzFRZgFoqf6Zh1LX8" "-" "-" "-"  LtpaToken:"-"
docserver - - [03/Apr/2018:12:55:45 +0300] "GET /__dbRID.nsf/0/unid/%24FILE/attName.docx HTTP/1.1" 200 4080 "GET /__dbRID.nsf/redirect.xsp?token=token&url=https://server/__dbRID.nsf/0/unid/$FILE/attName.docx" "-" "-" "-" "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjI3NDkzNDYsImV4cCI6MTUyMjc0OTY0Nn0.T4jpZ0y92Kw4iwS__8hQNgYjFXjzFRZgFoqf6Zh1LX8" "-" "-" "-"  LtpaToken:"-"
т.е. доксервер тупо убивает куки, и баянит JWT (Bearer) в base64:
JSON:
{"alg":"HS256","typ":"JWT"}
{"iat":1522749346,"exp":1522749646}
чего хотели этим разрабы ONLYOFFICE - загадка
[automerge]1522771659[/automerge]
по пути пришлось еще надрать куски для разрешения SSL , т.к. ИБМ корневые сертификаты патчит лениво
макет есть и работает, надо потоки Input/OutputStream скоординировать и выдать их клиенту...
вот хидер полученный на хэпаге
Код:
domino - - [03/Apr/2018:18:58:17 +0300] "GET GET /__dbRID.nsf/0/unid/%24FILE/attName.docx HTTP/1.1" 200 789961 "-" "Java/1.6.0" "-" "-" "-" "-" "-" "-"  LtpaToken:"request token"
 
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
допилил концепт, рассказываю...
Схематично будет так:
- на странице oo_read.xsp формируем ссылку для чтения из базы файла, но ссылка содержит имя др. страницы redirect_att
- здесь же получаем token (LtpaToken), страница oo_read доступна только авторизованным юзерам, token генерим для текущего, LtpaGenerator(user); в коде выше. Нужна библиотека на java, кот. размещаем в БД в разделе Java
- параметрами для redirect_att: token=...&unid=...
- redirect_att по параметрам создает запрос http к БД
- получив ответ в виде стрима - перенаправляем его в выходной стрим страницы redirect_att
сслыки для чтения (если интересно):
- вывод стрима в выходной поток страницы
- (взял только апачевскую либу, , её вставляем в раздел Jars, дизайне БД)
- чтение из SSJS по урл
- заморочки с SSL, о кот. говорил выше и . Записываем в jvm/lib/security/java.pol , передергиваем http на домине (te http quit/load http) чтобы рестартануть jvm для хэпагов
Код:
grant codeBase "xspnsf://server:0/<path to db in domino folder>/-" {
        permission java.security.AllPermission;
}
код SSJS либы (SSJSUtils)
JavaScript:
//https://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
var errAttr="error!->";
function LtpaGenerator(userDN){
  importPackage(lmike.org);
  try{
    var ltpa:LtpaGenerator1=new LtpaGenerator1();
    if (arguments.length > 1){
      ltpa.initByConfiguration(sessionAsSigner,arguments[1]);
    }else{
      ltpa.initByConfiguration(sessionAsSigner);
    }
    var token=ltpa.generateLtpaToken(userDN);
    return token;
  }catch(e){
    return errAttr+e.toString();
  }
}
function requestURL(urlStr, cookies){
  //importPackage(lmike.org);
  try{
  print("installing TrustAllCertificates...");
  lmike.org.TrustAllCertificates.install();
  print("TrustAllCertificates have been installed");
  var url = new java.net.URL(urlStr);
  var conn:java.net.HttpURLConnection = url.openConnection();
  conn.setRequestProperty("Cookie", cookies);
  //conn.setRequestProperty("Accept", "application/json");
  print("url:"+urlStr);
  print("geting response...");
  if (conn.getResponseCode() == "200") {
    print("status is OK");
    return conn.getHeaderFields();
    // Get the response
    var reader = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
    var buffer = new java.lang.StringBuffer();
    var line = "";
    while ((line = reader.readLine()) != null) {
      buffer.append(line);
    }
    reader.close();
    return buffer;
    // Create array from response
    var jsonarray = eval('(' + buffer + ')');
  
    // Get filenames and titles from Domino database collection resource
    // On XPage, requestScope.status is bound to a multi-line text control
    for (var i = 0; i < jsonarray.length; i++) {
      requestScope.status += jsonarray[i].@filepath + " - " + jsonarray[i].@title + "\n";
    }
  
  } else { // if connection fails
    requestScope.status = conn.getResponseCode() + " " + conn.getResponseMessage();
    print("request status:"+requestScope.status);
    return {"status":requestScope.status,
      "error":conn.getResponseCode()};
  }
  }catch(e){
    print(e.toString());
    return {"status":errAttr,
        "error":e.toString()
    }
  }
}

function url2Output(token, unid, att){
  var url = context.getUrl();
  var host = url.getHost();
  var domain=host.match(/\..*/)[0];
  var httpURL = database.getHttpURL();
  var dbUrl= httpURL.replace("http://", "https://").match(/(.+)\?/)[1];
  print("url:"+dbUrl);
  var target=dbUrl + "/0/"+unid+"/$FILE/"+att;
  var cookies="LtpaToken=" + token + "; domain=" + domain + "; path=/";
  try{
    print("installing TrustAllCertificates...");
    lmike.org.TrustAllCertificates.install();
    print("TrustAllCertificates have been installed");
    var url = new java.net.URL(target);
    var conn:java.net.HttpURLConnection = url.openConnection();

    conn.setRequestProperty("Cookie", cookies);
    //conn.setRequestProperty("Accept", "application/json");
    print("url:"+target);
    print("geting response...");
    if (conn.getResponseCode() == "200") {
      print("status is OK");
      //return conn.getHeaderFields();
      copyAttach2Output(conn,att);
    }
  }catch(e){
    print(e.toString());
    return {"status":errAttr,
         "error":e.toString()
    }
  }
}

function copyAttach2Output(conn, att){
  try{
  var externalContext:javax.faces.context.ExternalContext = facesContext.getExternalContext();
  var response:javax.servlet.http.HttpServletResponse = externalContext.getResponse();
  response.setHeader("Cache-Control", "no-cache");
  response.setDateHeader("Expires", -1);
  response.setContentType(conn.getContentType());
  response.setHeader("Content-Disposition", "attachment; filename=" + att);
  var outStream:java.io.OutputStream = response.getOutputStream();
  var inStream:java.io.InputStream = conn.getInputStream();
  org.apache.commons.io.IOUtils.copy(inStream,outStream)
  inStream.close();
  outStream.close();
  facesContext.responseComplete();
  }catch(e){
    print(e.toString());
   
    return {"status":errAttr,
        "error":e.toString()
    }
  }
}
код страницы для redirect_att oo_read в аттачах (движок форума так и глючит)
далее планирую сделать генерацию JWT на сервере (SSJS), в данный момент не считаю критичным
и запилить сохранение аттача в док, вот здесь вижу неприятные моменты, а именно - домина не предоставляет нормальный outputStream для аттача, хотя я это обходил, через майм
[automerge]1522859471[/automerge]
для интеграции с др. хэпагами юнид можно получать var unid=context.getUrlParameter('documentId');
тогда, например, в dynamic view panel указать как Page страницу oo_read и все заработает само ;)
[automerge]1522861192[/automerge]
по необъяснимой причине (для меня) достаточно часто LtpaToken получается просроченным
я не анализирую хидер ответа, хотя ужа там будет ясно, если контент типа text/html - получили страницу запроса имени пароля с уведомлением об просроченной сессии (если что-то сломалось - можно и просто запрос на аутентификацию получть, но у меня было только при ошибках)
[automerge]1522861781[/automerge]
еще понаблюдаю, быстофиксом оказалось //setDuration(ssoDoc.getItemValueInteger(SSO_DOMINO_DURATIONFIELD));
есть подозрение что оно фуфел гонет название поля правильное зашито в константу LTPA_TokenExpiration
[automerge]1522925944[/automerge]
полюбасу всплывает - устарела сессия, не знаю уже что делать
самое смешное - несколько попыток (перегенерится токен) и все норм
делать кастыль в виде попыток генерить токет по кругу - не хочется
[automerge]1522926087[/automerge]
ябы грешил на проксик, но с браузером нет такой фигни...
 

Вложения

  • redirect_att_1.zip
    484 байт · Просмотры: 210
  • oo_read.xsp.zip
    1,1 КБ · Просмотры: 230
Последнее редактирование:
  • Нравится
Реакции: duchan, Vertigo и alexas1

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
ни к чему определенному не пришел...
решил передавать текущий (с котю тек юзер) токен и SessionID в куках см. oo_read.xsp_1.zip
SSJUtils дополнил кодом для кукисов (сслыка на оригинал там) и передачей SessionID
JavaScript:
//http://lpar.ath0.com/2013/05/03/cookies-with-xpages/
var Cookies = (function () {
 /**
  * Get a cookie value as a Cookie object.
  */
 var get:javax.servlet.http.Cookie = function (cookiename:string) {
   return cookie.get(cookiename);
 },

 /**
  * Convenience method to get a cookie value as a string.
  */
 getString:string = function (cookiename:string) {
   return cookie.get(cookiename).getValue();
 },

 /**
  * Set a cookie value. Optional max age is in seconds.
  * Must be called during at least a partial update.
  */
 set = function (cookiename:string, value, maxagesecs:double) {
   var xcon = facesContext.getExternalContext();
   resp = xcon.getResponse(), cmap = xcon.getRequestCookieMap(), ck;
   if (cmap.containsKey(cookiename)) {
     ck = cmap.get(cookiename);
     ck.setValue(value);
   } else {
     ck = new javax.servlet.http.Cookie(cookiename, value);
   }
   if (typeof maxagesecs !== 'undefined') {
     ck.setMaxAge(maxagesecs);
   }
   resp.addCookie(ck);
 };

 return {'get': get, 'set': set, 'getString': getString};
}());
//https://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
var errAttr="error!->";
var oo_read="oo_read_batch.xsp";
function LtpaGenerator(userDN){
  importPackage(lmike.org);
  try{
    var ltpa:LtpaGenerator1=new LtpaGenerator1();
    if (arguments.length > 1){
      ltpa.initByConfiguration(sessionAsSigner,arguments[1]);
    }else{
      ltpa.initByConfiguration(sessionAsSigner);
    }
    var token=ltpa.generateLtpaToken(userDN);
    return token;
  }catch(e){
    return errAttr+e.toString();
  }
}
function requestURL(urlStr, cookies){
  //importPackage(lmike.org);
  try{
  print("installing TrustAllCertificates...");
  lmike.org.TrustAllCertificates.install();
  print("TrustAllCertificates have been installed");
  var url = new java.net.URL(urlStr);
  var conn:java.net.HttpURLConnection = url.openConnection();
  conn.setRequestProperty("Cookie", cookies);
  //conn.setRequestProperty("Accept", "application/json");
  print("url:"+urlStr);
  print("geting response...");
  if (conn.getResponseCode() == "200") {
    print("status is OK");
    return conn.getHeaderFields();
    // Get the response
    var reader = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
    var buffer = new java.lang.StringBuffer();
    var line = "";
    while ((line = reader.readLine()) != null) {
      buffer.append(line);
    }
    reader.close();
    return buffer;
    // Create array from response
    var jsonarray = eval('(' + buffer + ')');
  
    // Get filenames and titles from Domino database collection resource
    // On XPage, requestScope.status is bound to a multi-line text control
    for (var i = 0; i < jsonarray.length; i++) {
      requestScope.status += jsonarray[i].@filepath + " - " + jsonarray[i].@title + "\n";
    }
  
  } else { // if connection fails
    requestScope.status = conn.getResponseCode() + " " + conn.getResponseMessage();
    print("request status:"+requestScope.status);
    return {"status":requestScope.status,
      "error":conn.getResponseCode()};
  }
  }catch(e){
    print(e.toString());
    return {"status":errAttr,
        "error":e.toString()
    }
  }
}
function getDbUrl(){
      var httpURL = database.getHttpURL();
      var dbUrl= httpURL.replace("http://", "https://").match(/(.+)\?/)[1];
}

function url2Output(token, unid, att){
  var url = context.getUrl();
  var host = url.getHost();
  var domain=host.match(/\..*/)[0];
  var httpURL = database.getHttpURL();
  var dbUrl= httpURL.replace("http://", "https://").match(/(.+)\?/)[1];
  print("url:"+dbUrl);
  var target=dbUrl + "/0/"+unid+"/$FILE/"+att;
  var cookies="";
  if (token.contains(".")){
    var s=token.split(".")
    token=s[0];
    cookies="SessionID=" + s[1] + "; ";
  }
  cookies+="LtpaToken=" + token + "; domain=" + domain + "; path=/";
  print("Cookies would be set to:" + cookies);
  try{
    print("installing TrustAllCertificates...");
    lmike.org.TrustAllCertificates.install();
    print("TrustAllCertificates have been installed");
    var url = new java.net.URL(target);
    var conn:java.net.HttpURLConnection = url.openConnection();
    conn.setRequestProperty("Cookie", cookies);
    //conn.setRequestProperty("Accept", "application/json");
    print("url:"+target);
    print("geting response...");
    if (conn.getResponseCode() == "200") {
      print("status is OK");
      //return conn.getHeaderFields();
      copyAttach2Output(conn,att);
    }
  }catch(e){
    print(e.toString());
    return {"status":errAttr,
         "error":e.toString()
    }
  }
}

function copyAttach2Output(conn, att){
  try{
  var externalContext:javax.faces.context.ExternalContext = facesContext.getExternalContext();
  var response:javax.servlet.http.HttpServletResponse = externalContext.getResponse();
  response.setHeader("Cache-Control", "no-cache");
  response.setDateHeader("Expires", -1);
  var type=conn.getContentType();
  if (type.startsWith("text/html;")){
      
  }
  response.setContentType(type);
  response.setHeader("Content-Disposition", "attachment; filename=" + att);
  var outStream:java.io.OutputStream = response.getOutputStream();
  var inStream:java.io.InputStream = conn.getInputStream();
  org.apache.commons.io.IOUtils.copy(inStream,outStream)
  inStream.close();
  outStream.close();
  facesContext.responseComplete();
  }catch(e){
    print(e.toString());
    
    return {"status":errAttr,
        "error":e.toString()
    }
  }
}
сработало и на экпайред, пока, не жалуется
внятного манула как создавать и испотльзвать SessionID я не нашел и понимания - почему сессия, иногда, считается исекшей - тоже нет идей :(
Т.о. генерить токен больше не надо, но неизвестно - как оно пойдет дальше
[automerge]1522943286[/automerge]
капитанство...
в целях сбора серверных переменных, в одно место, можно использовать CustomControl и его вставлять на страницы
Код:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.beforeRenderResponse><![CDATA[#{javascript:var userDN=@UserName();
var userName=@Name("[CN]",userDN);}]]></xp:this.beforeRenderResponse>
    <xp:scriptBlock>
        <xp:this.value><![CDATA[var userName="#{javascript:userName}";
var userDN="#{javascript:userDN}";]]></xp:this.value>
    </xp:scriptBlock>
</xp:view>
для отображения юзера в документе есть стр-ра (JSON) в config
JavaScript:
  var config={
      "document": {
          "fileType": ext,
          "key": key,
          "title": title,
          "url": url
      },
      "editorConfig": {
        "mode": "open",
        "lang": "ru",
        "user": {
          "id": 2,
          "name": userName
        }
      },
      "documentType": documentType
  };
mode open - это для того чтобы не требовало калбэк для сохранения
[automerge]1522945942[/automerge]
еще нужно сделать "уникальный" id пользователя(ну мало ли чего этому OnO понадобится)
JavaScript:
    String.prototype.hashCode = function() {
      var hash = 0, i, chr;
      if (this.length === 0) return hash;
      for (i = 0; i < this.length; i++) {
        chr   = this.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
      }
      return hash;
    };
входом сделаем userDN
[automerge]1522950386[/automerge]
рассмотрим запись документа
JavaScript:
function request2DB(token, unid, att){
  try{
    print("Receiving request...");
    var externalContext:javax.faces.context.ExternalContext = facesContext.getExternalContext();
    var request:javax.servlet.http.HttpServletRequest = externalContext.getRequest();
    print("Content type:"+request.getContentType());
    print("Data size:"+request.getContentLength());
    var response:javax.servlet.http.HttpServletResponse = externalContext.getResponse();
    var reader = new java.io.BufferedReader(new java.io.InputStreamReader(request.getInputStream()));
    var buffer = new java.lang.StringBuffer();
    var line = "";
    while ((line = reader.readLine()) != null) {
      print(line);
    }
    print("Reques has processed");
    reader.close();
    var writer:java.io.PrintWriter = response.getWriter();
    writer.write("{\"error\":0}");
  }catch(e){
    _dump(e);
  }
}
тупо смотрим что отправляет доксервер и отвечам (если не ответить - будет ругацо)
[automerge]1522950618[/automerge]
страница редайректа аналогична redirect_att_edit
[automerge]1522950925[/automerge]
как понимаю - получени отредактированного дока будет полностью аналогично тому, что уже делал - получить по урлу контент, записать в БД используя идентификатор документа (я его генерил из юнида+время, т.е. тупо возьму 32 символа и далее...)
[automerge]1522950992[/automerge]
примеры сохранения дока (я буду на SSJS с примесью java)
[automerge]1523014230[/automerge]
Сделал сохранение...
взял доп. либы java (уже выкладывал)
Java:
package com.setralubs;
//source from http://www.nsftools.com/tips/RichTextOutputStream.java
import java.io.IOException;
import java.io.OutputStream;
import lotus.domino.*;

import java.util.*;
import java.io.*;

public class RichTextOutputStream extends OutputStream {
    //private static final int MAX_BUF = 524288;//2048;
    private static final int MAX_BUF = 2048;
    private RichTextItem rtitem;
    private boolean convertLF = true;
    private boolean saveOnClose = false;
    private Session ses =null;
    private Stream stream =null;
    //private Database db=null;
    private MIMEEntity body =null;
    private String fileName="defname";
    private byte[] buffer=new byte[MAX_BUF];
    private int count;
    
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    String getFileName() {
        return fileName;
    }

    /**
     * Create a RichTextOutputStream using the specified Notes RichTextItem
     * as a target.
     *
     * @param  item     the RichTextItem that we're writing to
     * @throws NotesException
     */
    public RichTextOutputStream (RichTextItem item) throws NotesException {
        this(item, true, false);
    }
    
    /**
     * Create a RichTextOutputStream using the specified Notes RichTextItem
     * as a target, specifying whether or not linefeeds should automatically be
     * converted to new RichText lines as they are encountered.
     *
     * @param  item     the RichTextItem that we're writing to
     * @param  convertLinefeeds     a boolean value indicating whether linefeeds
     *                                          should be converted to new RichText lines
     *                                          (true, the default behavior) or sent to the
     *                                          RichTextItem as raw characters (false)
     * @throws NotesException
     */
    public RichTextOutputStream (RichTextItem item, boolean convertLinefeeds) throws NotesException {
        this(item, convertLinefeeds, false);
    }
    
    /**
     * Create a RichTextOutputStream using the specified Notes RichTextItem
     * as a target, specifying whether or not linefeeds should automatically be
     * converted to new RichText lines as they are encountered, and whether
     * or not the document that the RichTextItem is in should be automatically
     * saved when the stream is closed using the close() method.
     *
     * @param  item     the RichTextItem that we're writing to
     * @param  convertLinefeeds     a boolean value indicating whether linefeeds
     *                                          should be converted to new RichText lines
     *                                          (true, the default behavior) or sent to the
     *                                          RichTextItem as raw characters (false)
     * @param  shouldSave               a boolean value indicating whether the RichTextItem's
     *                                          parent Document should be saved when the close()
     *                                          method is called (true) or not (false, the default behavior)
     * @throws NotesException
     */
    public RichTextOutputStream (RichTextItem item, boolean convertLinefeeds, boolean shouldSave) throws NotesException {
        rtitem = item;
        setConvert(convertLinefeeds);
        setSaveOption(shouldSave);
    }
    
    public RichTextOutputStream(String name, Document doc) throws NotesException {
        ses=NotesFactory.createSession();
        stream=ses.createStream();
        ses.setConvertMIME(false);
        //db=doc.getParentDatabase();
        body=doc.createMIMEEntity(name);
        //rtitem=doc.createRichTextItem(name);
    }
    
    public RichTextOutputStream(Session ses, String name, Document doc) throws NotesException {
        this.ses=ses;
        stream=ses.createStream();
        ses.setConvertMIME(false);
        //db=doc.getParentDatabase();
        body=doc.createMIMEEntity(name);
        //rtitem=doc.createRichTextItem(name);
    }
    
    /**
     * Changes the behaivor of the stream, so that it either automatically converts
     * \r and \n linefeeds to new RichText lines when they are encountered (the
     * default behavior) or not. Note that this behavior can be changed at any time
     * as the stream is being written to, although it affects only new data that is written
     * to the RichTextItem, not data that has already been written.
     *
     * @param  convertLinefeeds     a boolean value indicating whether linefeeds
     *                                          should be converted to new RichText lines
     *                                          (true, the default behavior) or sent to the
     *                                          RichTextItem as raw characters (false)
     */
    public void setConvert (boolean convertLinefeeds) {
        convertLF = convertLinefeeds;
    }
    
    /**
     * Changes the behavior of the stream when the close() method is called,
     * so that it either causes the Document that contains the RichTextItem to be
     * saved or it doesn't (default is that it doesn't).
     *
     * @param  shouldSave               a boolean value indicating whether the RichTextItem's
     *                                          parent Document should be saved when the close()
     *                                          method is called (true) or not (false, the default behavior)
     */
    public void setSaveOption (boolean shouldSave) {
        saveOnClose = shouldSave;
    }
    
    /**
     * Returns the RichTextItem that the stream is currently writing to; convenient if
     * you've passed the RichTextOutputStream to a method and you need to access
     * the underlying RichTextItem directly.
     *
     * @return    the Notes RichTextItem that this stream writes to
     */
    public RichTextItem getRichTextItem () {
        return rtitem;
    }
    
    /**
     * Closes the RichTextOutputStream, so that no more data can be written to it.
     */
    public void close () throws IOException {
        flush();
        //System.out.println("Closing...");
        if (saveOnClose)
            save();
        rtitem = null;
        super.close();
    }
    
    /**
     * Overrides the default flush() method in the OutputStream class, although this
     * method really does nothing, because we're not buffering data and we're always
     * writing directly to the underlying RichTextItem.
     */
    public void flush () {
        // since we're always writing directly to the RichTextField,
        // this doesn't really do anything
        //System.out.println("Flusing...");
        try {
            if (count>0) {flush2Stream(buffer);}
            if (stream!=null && stream.getBytes()>0) {
                System.out.format("RT Flushing: %s ...\n",getFileName());
                final MIMEEntity child = body.createChildEntity();
                MIMEHeader header = child.createHeader("Content-Disposition");
                header.setHeaderVal("attachment; filename=\"" + getFileName() + "\"");
                String fileSuffix = getFileName().substring(getFileName().lastIndexOf(".")+1);
                child.setContentFromBytes(stream, MIMEType.get(fileSuffix), MIMEEntity.ENC_IDENTITY_BINARY);
                stream.close();
                header.recycle();child.recycle();body.recycle();stream.recycle();
            }
        } catch (NotesException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    /**
     * Saves the RichTextItem we're working on by saving the Document that
     * contains it.
     */
    public boolean save () throws IOException {
        // a convenience method to save the Document we're working on
        // (we could have done this with the flush() method, but that could
        // cause unexpected results if this OutputStream is wrapped in
        // another Stream or Writer that auto-flushes)
        try {
            return rtitem.getParent().save();
        } catch (NotesException e) {
            // turn our NotesExceptions into IOExceptions
            throw new IOException(e.getMessage());
        }
    }
    
    /**
     * Writes an array of bytes to the RichTextItem (converting it first to a String).
     *
     * @param  b        an array of bytes
     */
    public void write (byte[] b) throws IOException {
        if (rtitem==null && body!=null) {
            super.write(b);
        }else {
            write(new String(b));
        }
    }
    
    /**
     * Writes len bytes from a byte array to the RichTextItem, starting at offset off
     * (converting it first to a String).
     *
     * @param  b        an array of bytes
     * @param  off      the start offset in the data
     * @param  len      the number of bytes to write
     */
    public void write (byte[] b, int off, int len) throws IOException {
        if (rtitem==null && body!=null) {
            super.write(b, off, len);
        }else {
            write(new String(b, off, len));
        }
    }
    
    /**
     * Writes a single byte to the RichTextItem (converting it first to a String).
     *
     * @param  b        a single byte
     * @throws NotesException
     */
    public void write (byte b) throws IOException, NotesException {
        if (rtitem==null && body!=null) {
            if (count<MAX_BUF) {
                buffer[count]=b;count++;
            }else {
                flush2Stream(buffer);
                buffer[0]=b;count=1;
            }
            //stream.write(buffer);
        }else {
            write(String.valueOf((char)b));
        }
    }
    
    private void flush2Stream(byte[] bytesIn) throws NotesException {
        // TODO Auto-generated method stub
        //optimization for memory operation
        if (stream==null)return;
        if (count+1<MAX_BUF) {
            //copy when buffer isn't full
            byte[] buff=Arrays.copyOf(bytesIn, count);
            stream.write(buff);
        }
        else {
            stream.write(bytesIn);
        }
        count=0;
        //Arrays.fill( bytesIn, (byte)0 );
    }

    /**
     * Writes a String to the RichTextItem.
     *
     * @param  str      a String to write
     */
    public void write (String str) throws IOException {
        
        try {
            // make sure we have a RichTextItem to write to
            if (rtitem == null)
                throw new IOException("The RichTextItem that you are trying to write to is null (maybe close() was called)");
            
            // and make sure we have a String to write
            if (str == null)
                return;
            
            // if we're not converting linefeeds, all we have to do is write
            // the String; otherwise, we'll need to tokenize it and replace
            // \r and \n with addNewLine calls (making sure to call it only
            // once for Windows \r\n linefeed combinations)
            if (!convertLF) {
                rtitem.appendText(str);
            } else {
                boolean wroteCR = false;
                String token = "";
                StringTokenizer st = new StringTokenizer(str, "\r\n", true);
                
                while (st.hasMoreTokens()) {
                    token = st.nextToken();
                    if (token.equals("\r")) {
                        rtitem.addNewLine(1);
                        wroteCR = true;
                    } else if (token.equals("\n")) {
                        if (!wroteCR)
                            rtitem.addNewLine(1);
                        wroteCR = false;
                    } else {
                        rtitem.appendText(token);
                        wroteCR = false;
                    }
                }
            }
        } catch (NotesException e) {
            // if we got a NotesException writing to the RichTextField,
            // turn it into an IOException and throw it (this way our
            // OutputStream is only throwing IOExceptions, which is
            // fairly standard)
            throw new IOException(e.getMessage());
        }
    }
    
    /**
     * Writes an entire InputStream to the RichTextItem (converting its contents to a String).
     *
     * @param  in       an InputStream
     * @throws NotesException
     */
    public void write (InputStream in) throws IOException, NotesException {
        int howManyBytes;
        byte[] bytesIn = new byte[2048];
        while ((howManyBytes = in.read(bytesIn)) >= 0) {
            stream.write(bytesIn);
            Arrays.fill( bytesIn, (byte)0 );
        }
        flush();

/*        while ((howManyBytes = in.read(bytesIn)) >= 0)
            write(bytesIn, 0, howManyBytes);
*/
        }
    
    /**
     * Writes a String to the RichTextItem, followed by a RichText newline.
     *
     * @param  str      a String to write
     */
    public void writeLine (String str) throws IOException {
        write(str);
        try {
            rtitem.addNewLine(1);
        } catch (NotesException e) {
            // turn our NotesExceptions into IOExceptions
            throw new IOException(e.getMessage());
        }
    }
    
    /**
     * Writes an attachment or object to the RichTextItem, using the parameters that are
     * called for in the NotesRichTextItem.embedObject() method (see this method in the
     * Notes Designer Help for more information about the parameters and usage).
     *
     * @param  type     either EmbeddedObject.EMBED_ATTACHMENT,
     *                          EmbeddedObject.EMBED_OBJECT, or
     *                          EmbeddedObject.EMBED_OBJECTLINK
     * @param  objClass     the name of an application (for EmbeddedObject.EMBED_OBJECT)
     *                              or null
     * @param  source       the name of the file to attach or link to
     * @param  name     either a name by which you want to refer to the EmbeddedObject,
     *                          or null.
     * @return  EmbeddedObject      a Notes EmbeddedObject reference to the file that
     *                                          was attached
     */
    public EmbeddedObject writeAttachment (int type, String objClass, String source, String name) throws IOException {
        try {
            return rtitem.embedObject(type, objClass, source, name);
        } catch (NotesException e) {
            // turn our NotesExceptions into IOExceptions
            throw new IOException(e.getMessage());
        }
    }
    
    /**
     * Changes the RichTextStyle of the text that will be written to the underlying
     * RichTextItem. Note that this only affects data that is written after this method
     * is called, not data that has already been written.
     *
     * @param  style        a Notes RichTextStyle that should be used to format
     *                          data that is written to this stream
     */
    public void setStyle (RichTextStyle style) throws IOException {
        try {
            rtitem.appendStyle(style);
        } catch (NotesException e) {
            // turn our NotesExceptions into IOExceptions
            throw new IOException(e.getMessage());
        }
    }
    
    /**
     * Changes the RichTextParagraphStyle of the text that will be written to the underlying
     * RichTextItem. Note that this only affects data that is written after this method
     * is called, not data that has already been written.
     *
     * @param  style        a Notes RichTextParagraphStyle that should be used to format
     *                          data that is written to this stream
     */
    public void setParagraphStyle (RichTextParagraphStyle pstyle) throws IOException {
        try {
            rtitem.appendParagraphStyle(pstyle);
        } catch (NotesException e) {
            // turn our NotesExceptions into IOExceptions
            throw new IOException(e.getMessage());
        }
    }

    @Override
    public void write(int b) throws IOException {
        try {
            write((byte)b);
        } catch (NotesException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
[automerge]1523015116[/automerge]
движок исчерпал свой лимит на пост - я теперь не могу прикладывать код, хоть новый топик заводи :(
[automerge]1523015238[/automerge]
напишите к-нить, что-то, чтобы новый пост мог раместить, ато мерджиг задолбал...
 

Вложения

  • oo_read.xsp_1.zip
    1,1 КБ · Просмотры: 211
  • redirect_att_edit.zip
    510 байт · Просмотры: 222
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
для JWT на сервере нужна либа совместимая с jvm 1.6
либа тянет json либу, взял в тырнетах уже собранную (2.2.1), сорц здесь
адаптированный (ключ до 32 бит дополняем 0) код
Java:
package lmike.org;
//https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-hmac
import java.io.UnsupportedEncodingException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

public class JWT {
  public String sign(String payload, String secret) {
    JWSSigner signer=null;
    String ret="";
 
    try {
//https://stackoverflow.com/questions/41882474/java-pad-a-string-with-ascii-0-null-to-a-multiple-of-16-bytes
      byte[] plainBytes = secret.getBytes("UTF-8");
      byte[] paddedBytes = new byte[32];
      System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
      signer = new MACSigner(paddedBytes);
 
    // Prepare JWT with claims set
      JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
        .subject(payload)
    //    .issuer("https://c2id.com")
    //    .expirationTime(new Date(new Date().getTime() + 60 * 1000))
        .build();
 
      SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
 
    // Apply the HMAC protection
      signedJWT.sign(signer);
 
    // Serialize to compact form, produces something like
    // eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA
      ret = signedJWT.serialize();
      System.out.println("::"+this.getClass().getName()+"::signed:"+ret);
    } catch (KeyLengthException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (JOSEException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
      return ret;
    }
}
я в сабж кладу имя юзера (бессмысленно класть туда конфиг, а остальные поля считаю лишними ;) )
[automerge]1523382452[/automerge]
подводя очередной итог (работает и чтение и редактирование):
- НЕ генерим LtpaToken, берем из куков, по текущему юзеру
- обязательно base64, почему-то в урл (если этого не сделать) он, иногда, "портиться" как мне показалось
- пока не привязываю ключ (в json config) только к юниду, разбавля. текущим временем, возможно надо добавлять base64 от времени модификации дока. если этого не делать - доксервер будет ругацо на версию документа. С версиями не разбирался
- после редактирования сохраняю док из привелигированной сессии (нвдо будет пределать проверку прав юзера, для сесуриту)

Описанную схему не получится реализовать на "классике" - у меня не заработал редайрект (т.е. доксервер не передавал куки), пришлось самому забирать аттач из бд (под токеном) и отдавать его доксерверу
Наковырял кучу либ для осуществления задуманного (java/javascript). Пока нет времени наработки перенести в отдельную БД
с хэпагами раз выстрелил себе в ногу наткнулся на фичу - даже закоменченный код, генеримый на странице (типа ${javascript:...}) может привести к непредсказуемому поведению страницы (будут пропадать переменные, не работать часть генераций...)
 
Последнее редактирование:
  • Нравится
Реакции: Vertigo и alexas1

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
расширил класс JWT ф-циями encrypt/decrypt (AES 128)
Java:
package lmike.org;
//https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-hmac
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

//import javax.crypto.KeyGenerator;
//import javax.crypto.SecretKey;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jose.crypto.*;

public class JWT {
  public String sign(String payload, String secret) {
    JWSSigner signer=null;
    String ret="";
    
    try {
//https://stackoverflow.com/questions/41882474/java-pad-a-string-with-ascii-0-null-to-a-multiple-of-16-bytes
      byte[] plainBytes = secret.getBytes("UTF-8");
      byte[] paddedBytes = new byte[32];
      System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
      signer = new MACSigner(paddedBytes);
    
    // Prepare JWT with claims set
      JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
        .subject(payload)
    //    .issuer("https://c2id.com")
    //    .expirationTime(new Date(new Date().getTime() + 60 * 1000))
        .build();
    
      SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
    
    // Apply the HMAC protection
      signedJWT.sign(signer);
    
    // Serialize to compact form, produces something like
    // eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA
      ret = signedJWT.serialize();
      System.out.println("::"+this.getClass().getName()+"::signed:"+ret);
    } catch (KeyLengthException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (JOSEException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
      return ret;
    }
  public String encrypt(String text, String key){
  //String text = "Hello World";
  //String key = "Bar12345Bar12345"; // 128 bit key
  // Create key and cipher
    Key aesKey = null;
    byte[] plainBytes = null;
    try {
      plainBytes = key.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }
    byte[] paddedBytes = new byte[16];
    System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
    aesKey = new SecretKeySpec(paddedBytes, "AES");
    Cipher cipher = null;
    try {
      cipher = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
        // encrypt the text
    try {
      cipher.init(Cipher.ENCRYPT_MODE, aesKey);
    } catch (InvalidKeyException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    byte[] encrypted = null;
    try {
      encrypted = cipher.doFinal(text.getBytes("UTF-8"));
    } catch (IllegalBlockSizeException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (BadPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("::"+this.getClass().getName()+"::encrypted size:"+encrypted.length);
    return DatatypeConverter.printBase64Binary(encrypted);
    //return new String(encrypted);
  }
 
  public String decrypt(String text, String key){
      // decrypt the text
    Key aesKey = null;
    byte[] plainBytes=null;
    try {
      plainBytes = key.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }
    byte[] paddedBytes = new byte[16];
    System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
    aesKey = new SecretKeySpec(paddedBytes, "AES");
    Cipher cipher = null;
    try {
      cipher = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    try {
      cipher.init(Cipher.DECRYPT_MODE, aesKey);
    } catch (InvalidKeyException e) {
      e.printStackTrace();
    }
    String decrypted = null;
    try {
      System.out.println("::"+this.getClass().getName()+"::encrypted b64 size:"+DatatypeConverter.parseBase64Binary(text).length);
      decrypted = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(text)));
    } catch (IllegalBlockSizeException e) {
      e.printStackTrace();
    } catch (BadPaddingException e) {
      e.printStackTrace();
    }
    //System.err.println(decrypted);
    return decrypted;
  }
 
}
ключи берем из БД
JavaScript:
function secretJWT(){
  var db:NotesDatabase=sessionAsSigner.getDatabase(session.getServerName(), session.getCurrentDatabase().getFilePath());
  var NV:NotesView=db.getView(OPTIONS);
  var doc:NotesDocument=NV.getDocumentByKey(OPT_SECRET);
//  print("optSecret UNID:"+doc.getUniversalID())
  secret=doc.getItemValueString(SECRETJWT_FLD);
  print("::secretJWT::secret:\""+secret+"\"");
  return secret;
}

function secretEXT(){
  var db:NotesDatabase=sessionAsSigner.getDatabase(session.getServerName(), session.getCurrentDatabase().getFilePath());
  var NV:NotesView=db.getView(OPTIONS);
  var doc:NotesDocument=NV.getDocumentByKey(OPT_SECRET);
//  print("optSecret UNID:"+doc.getUniversalID())
  secret=doc.getItemValueString(SECRETEXT_FLD);
  print("::secretEXT::secret:\""+secret+"\"");
  return secret;
}

function encrypt(text){
      importPackage(lmike.org);
      try{
        var jwt:JWT=new JWT();
        return jwt.encrypt(text, secretEXT());
      }catch(e){
        _dump(e);
      }

}
function decrypt(text){
      importPackage(lmike.org);
      try{
        var jwt:JWT=new JWT();
        return jwt.decrypt(text, secretEXT());
      }catch(e){
        _dump(e);
      }
}
шифрование будет нужно для передачи генерации токена из внешних приложений, шифровать предполагается имя юзера, это чтобы токен не могли генерить др. приложения, обращаясь на страницу (она будет под анонимом)
шифрование взял
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
для тех кто не связан религиозными убеждениями необходимостью использовать исключительно виндовс, можно установить доксервер так ONLYOFFICE/Docker-DocumentServer
и даже под виндой возможно поставить виртуалку, а в ней нужную системе ОС
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
текущий статус таков - у меня все работает
озаботился конвертацией, как и положено, для проектов с монитизацией на поддержке (каковым и является ONLYOFFICE), информацию по деталям приходится собирать по крупицам ;) из косвенных намеков и собственных размышлений :)
у меня установлен доступ к документ сереру (далее ДС) по токену, эпопею с токеном я описывал выше и в настоящее время он создается на сервере
остался вопрос - что там должно быть...
как получается для редактирования и открытия документов - достаточно чтобы он был (содержимое роли не играет), а вот для конвертации - это важно и куда его пихать, в доках не описано ;)
зато есть косвенное упоминание здесь
исследование показало- надо пихать его в хидер типа:
JavaScript:
    //https://stackoverflow.com/questions/3324717/sending-http-post-request-in-java
    result.urlConvert=urlconvert;
    result.status=+"connecting...";
   
    lmike.org.TrustAllCertificates.install();
    var url:java.net.URL = new java.net.URL(urlconvert);
    var http:java.net.HttpURLConnection=url.openConnection(); //getConnect(urlconvert);
    http.setRequestMethod("POST");
    http.setDoOutput(true);
    http.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
    http.setRequestProperty("Authorization", "Bearer "+result.jwt);
    http.connect();
    var bw:java.io.BufferedWriter=new java.io.BufferedWriter(new java.io.OutputStreamWriter(http.getOutputStream()));
    bw.write(result.requestData);
    bw.flush();
    bw.close();
т.е. вот эта строка http.setRequestProperty("Authorization", "Bearer "+result.jwt);
мало того - по ссылке видно, что содержимое д.б. в JWT и немного отличаться от тела запроса (json)
фактически тело будет { payload:<jsonbody>} и ообработанное через JWT подпись, я сделал
JavaScript:
    var jwt:lmike.or.JWT=new lmike.org.JWT();
    var jwtSigned=jwt.signPayload(toJson(payload), secretJWT());
    //toPDF.token=jwtSigned;
    result.jwt=jwtSigned;
    result.requestData=toJson(toPDF);
java либа
Java:
package lmike.org;
//https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-hmac
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

//import javax.crypto.KeyGenerator;
//import javax.crypto.SecretKey;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jose.crypto.*;

public class JWT {
  public String sign(String subject, String secret) {
    JWSSigner signer=null;
    String ret="";
   
    try {
//https://stackoverflow.com/questions/41882474/java-pad-a-string-with-ascii-0-null-to-a-multiple-of-16-bytes
      byte[] plainBytes = secret.getBytes("UTF-8");
      byte[] paddedBytes = new byte[32];
      System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
      signer = new MACSigner(paddedBytes);
   
    // Prepare JWT with claims set
      JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
        .subject(subject)
    //    .issuer("https://c2id.com")
    //    .expirationTime(new Date(new Date().getTime() + 60 * 1000))
        .build();
   
      SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
   
    // Apply the HMAC protection
      signedJWT.sign(signer);
   
    // Serialize to compact form, produces something like
    // eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA
      ret = signedJWT.serialize();
      System.out.println("::"+this.getClass().getName()+"::signed:"+ret);
    } catch (KeyLengthException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (JOSEException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
      return ret;
    }
  //https://www.massapi.com/class/com/nimbusds/jose/Payload.html
  public String signPayload(String payload, String secret) throws JOSEException, UnsupportedEncodingException {
    // Create HMAC signer
    JWSSigner signer = null;
    //new MACSigner(secret);
      byte[] plainBytes = secret.getBytes("UTF-8");
      byte[] paddedBytes = new byte[32];
      System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
      signer = new MACSigner(paddedBytes);

    // Prepare JWS object with "Hello, world!" payload
    JWSObject jwsObject = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build(), new Payload(payload));

    // Apply the HMAC
    jwsObject.sign(signer);
    String ret = jwsObject.serialize();
    return ret;

  }

  public String encrypt(String text, String key){
  //String text = "Hello World";
  //String key = "Bar12345Bar12345"; // 128 bit key
  // Create key and cipher
    Key aesKey = null;
    byte[] plainBytes = null;
    try {
      plainBytes = key.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }
    byte[] paddedBytes = new byte[16];
    System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
    aesKey = new SecretKeySpec(paddedBytes, "AES");
    Cipher cipher = null;
    try {
      cipher = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
        // encrypt the text
    try {
      cipher.init(Cipher.ENCRYPT_MODE, aesKey);
    } catch (InvalidKeyException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    byte[] encrypted = null;
    try {
      encrypted = cipher.doFinal(text.getBytes("UTF-8"));
    } catch (IllegalBlockSizeException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (BadPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("::"+this.getClass().getName()+"::encrypted size:"+encrypted.length);
    return DatatypeConverter.printBase64Binary(encrypted);
    //return new String(encrypted);
  }

  public String decrypt(String text, String key){
      // decrypt the text
    Key aesKey = null;
    byte[] plainBytes=null;
    try {
      plainBytes = key.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }
    byte[] paddedBytes = new byte[16];
    System.arraycopy(plainBytes, 0, paddedBytes, 0, plainBytes.length);
    aesKey = new SecretKeySpec(paddedBytes, "AES");
    Cipher cipher = null;
    try {
      cipher = Cipher.getInstance("AES");
    } catch (NoSuchAlgorithmException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchPaddingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    try {
      cipher.init(Cipher.DECRYPT_MODE, aesKey);
    } catch (InvalidKeyException e) {
      e.printStackTrace();
    }
    String decrypted = null;
    try {
      System.out.println("::"+this.getClass().getName()+"::encrypted b64 size:"+DatatypeConverter.parseBase64Binary(text).length);
      decrypted = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(text)));
    } catch (IllegalBlockSizeException e) {
      e.printStackTrace();
    } catch (BadPaddingException e) {
      e.printStackTrace();
    }
    //System.err.println(decrypted);
    return decrypted;
  }

}
в ней я и дописал отличие от просто - "подписать к-л фигню" signPayload (посмотрев исходник по ссылке в коде)
по ходу я еще столкнулся с "проблемой" получения строки из js объекта, но подсмотрел вывод в тырнетах по хэпагам и ф-ция toJson решает эту задачу (название прямскажем - путает)
осталось парсить хмл ответа от конвертора - обойдусь регэкспами, но заветную ссылку со сконверченным файлом я получил :)
полный код SSJS выложу попозже.
Для проверки токенов есть сервис (очень помог мне для анализа ситуации) прям там и секретный ключ можно подставить и все base64 посмотреть, я так и догадался про Authorization: Bearer, декодировав заголовки в примере по ссылке
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 941
609
BIT
216
поднимая вопрос про только чтение для документов, принципиально, в данном контексте, это возможно
что для это нужно:
- подписывать конфигурацию (json) нга стороне сервера и формировать jwt для DocEditor. Причина - чтобы хтрый юзер не заменил конфиг, по калбэку (от DocEditor) будем проверять токен
- используем такую конструкцию, в конфигурации
JSON:
var docEditor = new DocsAPI.DocEditor("placeholder", {
    "document": {
        "permissions": {
            "comment": false,
            "download": false,
            "edit": false,
            "print": false,
            "review": false
        },
        ...
    },
    ...
});
этим шагом мы запретим интерфейсные фичи
Конечно, таким способом нельзя запретить копирование текста, но очень осложняется к-л действия с контентом (будет только просмотр). Т.к. содержимое - есть js, с кучей объектов и рядовой юзер просто не сможет ч-л сделать (тупо неформатированный текс и постранично - это можно и с экрана перепечатывать ;) ), да для разработчика - это реальный вызов (ябы даже не взялся ковырять такое)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!