• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

CORS и Domino

lionk

Member
29.03.2021
10
0
BIT
0
Всем привет, чесноговоря думал что форум умер мого лет назад ... удевлён что ещё кто то остался :) (мой старый акк походу протух)

Вобщем, упросили меня сделать реновацию одного сервиса 8-ми летней давности, и я на свою голову согласился помоч. Благо там 9.0.1 крутится.
Ну вобщем я такой думаю щя REST на xpage слеплю, логику в SSJS либы и всё будет почти как в лучших бекендах Парижа и Лондона, а фронт делайте на чём хотите.
С этим всё гут. Но захотелось мне добавить гибкости и чтоб фронт крутился совсем отдельно а в домину ходил по отдельному домену, и всё жило совсем на разных серваках.
Знаю что простой путь - это поднять проксисервер, фронт будет ходить к нему а тот слать модифицированные запросы к domino.
Но ифраструктура там и так не блещит а домина живёт "потому что не падает".
Поэтому я решил настроить CORS на DOMINO. :(


И это оказалась подёмной задачей (на самом деле нет):

- в интернет сайтах создаём Web Site Rule - HTTP response headers
1-й для логина:
Incoming URL pattern: */names.nsf*
Custom headers:
Access-Control-Allow-Origin: https://codeby.net
Access-Control-Allow-Credentials: true

2-й для OPTIONS запросов при тригере реста:
Incoming URL pattern: */api.xsp*
Custom headers:
Access-Control-Allow-Origin: https://codeby.net
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE

- в коде xpage в beforePageLoad уже руками задаём все необходимые хедеры:
JavaScript:
var response=facesContext.getExternalContext().getResponse();
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Origin",sessionScope.config.AllowOrigin);
response.setHeader("Access-Control-Allow-Headers","Content-Type");
response.setHeader("Access-Control-Request-Method","DELETE, PUT, GET");


Такми образом из браузера делая:

JavaScript:
var formdata = new FormData();
formdata.append("username", document.getElementById('name').value);
formdata.append("password", document.getElementById('password').value);

var requestOptions = {
      method: 'POST', 
      body: formdata,
      redirect: 'manual',
      credentials: 'include'
    };

 fetch("http://mydomain.com/names.nsf?login", requestOptions)

выполняем логин и получаем в респонзе
Set-Cookie: DomAuthSessId=3616985AA8058118F10C2D2823279995; path=/
дальше можем делать запрос к апи, браузер будет слать куку и всё будет работать.

Круто да? Слава IBM!

но в реальной жизни есть проблемка.....
tldr; Standards related to the Cookie SameSite attribute recently changed
Вобщем в авторизационной куке должен быть SameSite attribute, без него браузены её начинают игнорировать, и усё ... CORS есть а авторизации - нет, ибо браузер игнорит куку.
Я смутно помню что переопределить алгоритм логина не возможно, токо формучку кастомизировать в domcfg.nsf

но может кто то подскажет как быть в такой ситуации? в прошлом форум выручал.
 

NetWood

Lotus Team
17.04.2008
545
93
BIT
8
Таки прилепите на страничку любую кукулибу на jQuery, например, и пишите в ней SameSite руками как и другие куки.
Например, я немного сам кастомизировал jquery.cookie.js
Код:
return (document.cookie = [
                encode(key), '=', stringifyCookieValue(value),
                options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
                options.path    ? '; path=' + options.path : '',
                options.domain  ? '; domain=' + options.domain : '',
                options.secure  ? '; secure' : '',
                options.samesite  ? '; samesite=' + options.samesite : '' //NetWood 2020-12-06
            ].join(''));
 

lionk

Member
29.03.2021
10
0
BIT
0
Таки прилепите на страничку любую кукулибу
Смутно представляю как это поможет при ассинхронных запросах на другой домен?
Разве при fetch("names.nsf/login") клиентский джаваскрипт с $$LoginUserForm будет выполнен?
Или я не понял на какаой странице делать переопределение сессионой куки....
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 933
609
BIT
177
Смутно представляю как это поможет при ассинхронных запросах на другой домен?
Разве при fetch("names.nsf/login") клиентский джаваскрипт с $$LoginUserForm будет выполнен?
Или я не понял на какаой странице делать переопределение сессионой куки....
решения мб разные
- делать куку самому по имени юзер , здесь описывалось и не раз её и возвращать браузеру. Вопрос авторизации юзера не рассматриваю
- сделать прокси на любом аппсервере, который будет осуществлять собственную авторизацию, а к домине ходить через $WSRU, тоже здесь описывалось
а если честно - я не понял почему просто не использовать доминошную авторизацию с мультисерверной сессией (LtpaToken в куках)
и надо было городить "прокси" для авторизации
и таки, да - в начале топика указан ток один тип авторизации - "односерверная" сессия
Как получать в томже реакте серверную и мультисерверную авторизацию ЕМНИП я тоже выкладывал, вот кусок
JavaScript:
app.post("/login", function(req, res, next) {
  const { Username, Password } = req.body;
  const options = {
    uri: `${protocol}://${servername}/names.nsf?login`,
    resolveWithFullResponse: true,
    form: { Username, Password },
    simple: false
  };
  rp
    .post(options)
    .then(function(response) {
      const { headers, body } = response;
      const dominoauthenticationfailure = headers.dominoauthenticationfailure;

      if (dominoauthenticationfailure) {
        // authentication failure
        return res.status(401).send(dominoauthenticationfailure);
      }

      // Decode cookies
      const cookies = setCookie.parse(response, {
        decodeValues: true
      });

      //filter cookies to only DomAuthSessId
      const domino_auth_cookie = cookies.filter(function(cookie) {
        return cookie.name === "DomAuthSessId" || cookie.name === "LtpaToken";
      });

      if (domino_auth_cookie.length > 0) {
        return res.send(domino_auth_cookie[0].value);
      }

      return res.send(body);
    })
    .catch(function(err) {
      console.log(err);
      return res.status(500).send(err);
    });
});
еще осталась не раскрытой тема - напаркуа писать рест, если есть есть смысл ток если бинарники отправлять (это точно невозможно на домину сделать без кода) /получать и нет желания (по каким-либо причинам) брать файлы по доминошному урл
 
Последнее редактирование:

lionk

Member
29.03.2021
10
0
BIT
0
я не понял почему просто не использовать доминошную авторизацию с мультисерверной сессией (LtpaToken в куках)
походу прийдётся (как руками генерить LtpaToken я в курсе) но это значит что прийдётся велосипедить что то типа hello.nsf с доступом анонимуса и /login /logout ендпоинтами. надеялся этого избежать.
Single Server - имеет приемущество что сам редиректит на логин страничку и обратно куда надо.
с мультисерверной уже самому нужно это обрабатывать

делать куку самому по имени юзер
вот буду премного благодарен если поделишся исходником генерации DomAuthSessId, таким как это происходит для LtpaToken.

напаркуа писать рест, если есть DAS
я его попытался включить, чесно . . . но это какой то неповоротливый комбайн которым я не понял как управлять.
может он удобен для прямой отдачи представлений. но как на нём бизнеслогику работы с документами строить? а потом эту логику поддерживать и модифицировать?
вобщем после пары часов гугления я тупо сдался. имхо restService -> customRestService на xpage мне кажется достаточно гибким, прозрачным и контролируемым.
я сразу отказался от viewCollectionJsonService и documentJsonService в первую очередь из за формата запроса и низкой мутабельности
XPagesExt.nsf/REST_DataService.xsp/documentPathInfo/unid/{docUnid} - ну что за унылая дубликация unid? понимаю, вкусовщина (но я стыкался с архитекторами которые чуть ли не морду бить лезли если контракт реста не согласовывается с бестпрактиками)
вобщем в моём случае логика такова:
restService | DAS
быстро, гибко, токо сдесь | громоздко но везде.
 

lionk

Member
29.03.2021
10
0
BIT
0
Как получать в томже реакте серверную и мультисерверную авторизацию ЕМНИП я тоже выкладывал,
важный нюанс:
ассинхронные запросы к names.nsf будут работать если сайт и домино крутятся на одном домене.
но если домены разные - то фронт не получити ничего (несмотря на то что домина отправит) из за ограниченй сендбокса.
если фронт и бек на одном домене - то всё работает из коробки, ничего велосипедить не надо.
но попробуй в хроме с локального компа сделать ассинхронный запрос к names.nsf
 

Вложения

  • 111.png
    111.png
    14,2 КБ · Просмотры: 117

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 933
609
BIT
177
Single Server - имеет приемущество что сам редиректит на логин страничку и обратно куда надо.
с мультисерверной уже самому нужно это обрабатывать
не понял - КМК тоже что и мультисерверноая
я его попытался включить, чесно . . . но это какой то неповоротливый комбайн которым я не понял как управлять.
может он удобен для прямой отдачи представлений. но как на нём бизнеслогику работы с документами строить? а потом эту логику поддерживать и модифицировать?
вобщем после пары часов гугления я тупо сдался. имхо restService -> customRestService на xpage мне кажется достаточно гибким, прозрачным и контролируемым.
в DAS ресты для всего, в т.ч. документов...
кастом сервисы, помимо утечек, имеют ток 1-ин + (условный) - урл не регламентирован (снаружи)
уж тогда можно бины брать (не помню откуда куски драл), вот делал обработку хухеля
Java:
package com.setralubs;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.List;
import java.util.logging.Level;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.openntf.domino.xsp.XspOpenLogUtil;

import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.ibm.xsp.http.UploadedFile;

public class FileBean implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1674320693117353905L;
    
    public void uploadLocalFile() {
System.out.println("Start file processing...");
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();

        HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();

        String fileUploadID = ExtLibUtil.getClientId(facesContext, facesContext.getViewRoot(), "fileUpload1", false);

        UploadedFile uploadedFile = ((UploadedFile) request.getParameterMap().get(fileUploadID));

        if (uploadedFile == null) {
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, "No file uploaded. Use the file upload button to upload a file.", ""));
            return;
        }

        java.io.File file = uploadedFile.getServerFile();
        String contentType=uploadedFile.getContentType();
        String fileName = uploadedFile.getClientFileName();

        PersonNAB personNAB=new PersonNAB();
        if (contentType.endsWith("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")){
            try {
                List<String> results=personNAB.processing(new FileInputStream(file), null);
                facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "file was uploaded: "+fileName+"/"+contentType, ""));
                for(String e:results){
                    facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, e, ""));
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                XspOpenLogUtil.logError(e);
                facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Error during uploading, file: "+fileName+"/"+contentType, ""));
            }
        }else{
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "file was NOT uploaded: "+fileName+"/"+contentType, ""));
        }
    }
}
XML:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:fileUpload id="fileUpload1"></xp:fileUpload>

<xp:button id="button1"
    value="Upload Local File">
    
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
    <xp:this.action>
        <xp:executeScript script="#{FileBean.uploadLocalFile}"></xp:executeScript>
    </xp:this.action></xp:eventHandler></xp:button>
    <xp:messages id="messages1"></xp:messages>
    
    </xp:view>
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 933
609
BIT
177
вот буду премного благодарен если поделишся исходником генерации DomAuthSessId, таким как это происходит для LtpaToken.
а чем вариант с $WSRU не подходит - пусть домина сам генерит по имени
 

lionk

Member
29.03.2021
10
0
BIT
0
кастом сервисы, помимо утечек
а можно тут по подробнее? где течёт и как у себя проверить?
глобально пока никаких проблем не заметил, но я их и не искал, если подскажешь как идентифицировать, то поищу и отпишу.
имеют ток 1-ин + (условный) - урл
как по мне это не недостаток.
в корне api.xsp я сделал табличку с описанием всего реста (типо свагер)
так то при запросе несуществуюющего апи - просто откроется текст "какое апи дозволено"

тогда можно бины брать
так кастом рест сервис их тоже поддерживает
XML:
<xe:restService id="myid" pathInfo="apipath">
    <xe:this.service>
        <xe:customRestService
            serviceBean="my.code.CustomServiceBean">
        </xe:customRestService>
    </xe:this.service>
</xe:restService>

и ещо важний момент:
я топлю за customRestService в моём конкретном случае - когда в итоге в веб кроме реста *ничего* смотреть не будет.
возможно если бы нужно было поддерживать вебморду и паралельно рест - то DAS может быть вариантом.

но опять же как через DAS реальзовать апи частичного изменения документа (ну типа PATCH /changeStatus?docuuid=XXX) лично для меня остаётся окрытым?
 
Последнее редактирование:

lionk

Member
29.03.2021
10
0
BIT
0
а чем вариант с $WSRU не подходит
это какаято чёрная лотусная магия которой я не обучен и боюсь.
сервак некому нормально обслуживать и уж темболе строить какуюто секурити инфраструктуру, так шо старый добрый LtpaToken наше всё.
 

savl

Lotus Team
28.10.2011
2 597
310
BIT
159
это какаято чёрная лотусная магия которой я не обучен и боюсь.
сервак некому нормально обслуживать и уж темболе строить какуюто секурити инфраструктуру, так шо старый добрый LtpaToken наше всё.
$WSRU это из способа интеграции WebSphera или как там называется этот продукт.
Вроде как дыра, с другой стороны везде пишут, в том числе и ibm, что нет, если юзать внутри и аккуратно.
Но так-то да. если получилось с ltpa, то лучше его и оставить.
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 933
609
BIT
177
$WSRU это из способа интеграции WebSphera или как там называется этот продукт.
Вроде как дыра, с другой стороны везде пишут, в том числе и ibm, что нет, если юзать внутри и аккуратно.
Но так-то да. если получилось с ltpa, то лучше его и оставить.
дык там не Ltpa - желание про односерверный вариант
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 933
609
BIT
177
это какаято чёрная лотусная магия которой я не обучен и боюсь.
сервак некому нормально обслуживать и уж темболе строить какуюто секурити инфраструктуру, так шо старый добрый LtpaToken наше всё.
Ltpa полученный через.... 10-тые руки ничем не лучше его же но через "прокси" на локалхрст домины, ток без заморочек по соответствию юзера, т.е. - сам к-хошь
 
Последнее редактирование:
Мы в соцсетях:

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