Fix eszkola.opolskie.pl login

This commit is contained in:
Mikołaj Pich 2021-09-28 18:35:26 +02:00
parent 5151856da1
commit f62736adb0
11 changed files with 182 additions and 113 deletions

View file

@ -7,6 +7,7 @@ import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS
import okhttp3.Interceptor
import okhttp3.Response
import org.jsoup.Jsoup
@ -38,8 +39,12 @@ class ErrorInterceptor : Interceptor {
}
}
doc.select(".ErrorMessage, #ErrorTextLabel, #loginArea #errorText").let {
if (it.isNotEmpty()) throw BadCredentialsException(it.text().trimEnd('.'))
doc.select(".ErrorMessage, #ErrorTextLabel, #loginArea #errorText").takeIf { it.isNotEmpty() }?.let {
val errorMessage = it.text().trimEnd('.')
if (doc.select(SELECTOR_ADFS).isNotEmpty()) {
if (errorMessage.isNotBlank()) throw BadCredentialsException(errorMessage)
else logger.warn("Unexpected login page!")
} else throw BadCredentialsException(errorMessage)
}
doc.select("#MainPage_ErrorDiv div").let {

View file

@ -80,7 +80,11 @@ class LoginHelper(
val login = if ("@" in email) email else "EDUPORTAL\\$email"
sendADFSMS(login, password)
}
else -> sendADFS(it, password)
"eszkola.opolskie.pl" -> {
val login = if ("@" in email) email else "EDUPORTAL\\$email"
sendADFSMS(login, password)
}
else -> sendADFSMS(it, password)
}
}
ADFSLight, ADFSLightScoped, ADFSLightCufs -> sendADFSLightGeneric(it, password, loginType)
@ -146,8 +150,32 @@ class LoginHelper(
)
}
private suspend fun sendADFS(email: String, password: String): CertificateResponse {
val res = api.getForm(getADFSUrl(ADFS))
private suspend fun sendADFSMS(email: String, password: String): CertificateResponse {
val res = api.sendADFSMSForm(
url = getADFSUrl(ADFS),
values = mapOf(
"UserName" to email,
"Password" to password,
"AuthMethod" to "FormsAuthentication"
)
)
val form = certificateAdapter.fromHtml(res)
return certificateAdapter.fromHtml(
api.sendADFSForm(
url = form.action,
values = mapOf(
"wa" to form.wa,
"wresult" to form.wresult,
"wctx" to form.wctx
)
)
)
}
private suspend fun sendADFSCards(email: String, password: String): CertificateResponse {
val res = api.getForm(getADFSUrl(ADFSCards))
if (res.formAction.isBlank()) throw VulcanException("Invalid ADFS login page: '${res.title}'. Try again")
val form = certificateAdapter.fromHtml(
@ -178,73 +206,6 @@ class LoginHelper(
)
}
private suspend fun sendADFSMS(email: String, password: String): CertificateResponse {
val res = api.sendADFSMSForm(
url = getADFSUrl(ADFS),
values = mapOf(
"UserName" to email,
"Password" to password,
"AuthMethod" to "FormsAuthentication"
)
)
val form = certificateAdapter.fromHtml(res)
return certificateAdapter.fromHtml(
api.sendADFSForm(
url = form.action,
values = mapOf(
"wa" to form.wa,
"wresult" to form.wresult,
"wctx" to form.wctx
)
)
)
}
private suspend fun sendADFSCards(email: String, password: String): CertificateResponse {
val form = api.getForm(getADFSUrl(ADFSCards))
val form2 = api.sendADFSFormStandardChoice(
url = "$schema://adfs.$host/${form.formAction.removePrefix("/")}",
formState = mapOf(
"__db" to form.db,
"__VIEWSTATE" to form.viewstate,
"__VIEWSTATEGENERATOR" to form.viewStateGenerator,
"__EVENTVALIDATION" to form.eventValidation,
"PassiveSignInButton.x" to "0",
"PassiveSignInButton.y" to "0"
)
)
val form3 = certificateAdapter.fromHtml(
api.sendADFSForm(
url = "$schema://adfs.$host/${form2.formAction.removePrefix("/")}",
values = mapOf(
"__db" to form2.db,
"__VIEWSTATE" to form2.viewstate,
"__VIEWSTATEGENERATOR" to form2.viewStateGenerator,
"__EVENTVALIDATION" to form2.eventValidation,
"SubmitButton.x" to "0",
"SubmitButton.y" to "0",
"UsernameTextBox" to email,
"PasswordTextBox" to password
)
)
)
return certificateAdapter.fromHtml(
api.sendADFSForm(
url = form3.action,
values = mapOf(
"wa" to form3.wa,
"wresult" to form3.wresult,
"wctx" to form3.wctx
)
)
)
}
private fun getADFSUrl(type: Scrapper.LoginType): String {
val id = when (type) {
ADFS -> if (host == "eduportal.koszalin.pl") "ADFS" else "adfs"

View file

@ -21,10 +21,9 @@ class AccountRepository(private val account: AccountService) {
companion object {
const val SELECTOR_STANDARD = ".loginButton, .LogOnBoard input[type=submit]" // remove second selector?
const val SELECTOR_ADFS = "form[name=form1] #SubmitButton"
const val SELECTOR_ADFS_MS = "form#loginForm"
const val SELECTOR_ADFS_LIGHT = ".submit-button, form #SubmitButton"
const val SELECTOR_ADFS_CARDS = "#PassiveSignInButton"
const val SELECTOR_ADFS = "#loginArea form#loginForm"
const val SELECTOR_ADFS_LIGHT = ".submit-button"
const val SELECTOR_ADFS_CARDS = "#__VIEWSTATE"
}
suspend fun getPasswordResetCaptcha(registerBaseUrl: String, symbol: String): Pair<String, String> {

View file

@ -16,7 +16,6 @@ import io.github.wulkanowy.sdk.scrapper.register.toSemesters
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_CARDS
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_LIGHT
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_MS
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_STANDARD
import io.github.wulkanowy.sdk.scrapper.service.MessagesService
import io.github.wulkanowy.sdk.scrapper.service.RegisterService
@ -63,6 +62,7 @@ class RegisterRepository(
private suspend fun getSymbols(): List<Pair<String, CertificateResponse>> {
val symbolLoginType = getLoginType(startSymbol.getNormalizedSymbol())
println("Register login type: $symbolLoginType")
val cert = loginHelper.apply { loginType = symbolLoginType }.sendCredentials(email, password)
return Jsoup.parse(cert.wresult.replace(":", ""), "", Parser.xmlParser())
@ -83,7 +83,7 @@ class RegisterRepository(
val page = register.getFormType(urlGenerator.generate(ServiceManager.UrlGenerator.Site.LOGIN) + "Account/LogOn").page
return when {
page.select(SELECTOR_STANDARD).isNotEmpty() -> Scrapper.LoginType.STANDARD
page.select(SELECTOR_ADFS).isNotEmpty() || page.select(SELECTOR_ADFS_MS).isNotEmpty() -> Scrapper.LoginType.ADFS
page.select(SELECTOR_ADFS).isNotEmpty() -> Scrapper.LoginType.ADFS
page.select(SELECTOR_ADFS_LIGHT).isNotEmpty() -> {
page.selectFirst("form")?.attr("action").orEmpty().run {
when {

View file

@ -27,10 +27,6 @@ interface LoginService {
@GET
suspend fun getForm(@Url url: String): ADFSFormResponse
@POST
@FormUrlEncoded
suspend fun sendADFSFormStandardChoice(@Url url: String, @FieldMap formState: Map<String, String>): ADFSFormResponse
@POST
@FormUrlEncoded
suspend fun sendADFSForm(@Url url: String, @FieldMap values: Map<String, String>): String

View file

@ -0,0 +1,53 @@
package io.github.wulkanowy.sdk.scrapper
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
import kotlinx.coroutines.runBlocking
import okhttp3.logging.HttpLoggingInterceptor
import org.junit.Ignore
import org.junit.Test
@Ignore
class HostsRemoteTest : BaseTest() {
private val knownHosts = listOf(
"vulcan.net.pl" to "Default",
"eszkola.opolskie.pl" to "opole",
"edu.gdansk.pl" to "gdansk",
"edu.lublin.eu" to "lublin",
"umt.tarnow.pl" to "tarnow",
"resman.pl" to "rzeszow",
"eduportal.koszalin.pl" to "koszalin",
"vulcan.net.pl" to "rawamazowiecka",
"vulcan.net.pl" to "zdunskawola",
"vulcan.net.pl" to "sieradz",
"vulcan.net.pl" to "skarzyskokamienna",
"vulcan.net.pl" to "lask",
"vulcan.net.pl" to "powiatlaski",
"vulcan.net.pl" to "powiatkrasnostawski",
"vulcan.net.pl" to "powiatketrzynski",
"vulcan.net.pl" to "gminaulanmajorat",
"vulcan.net.pl" to "gminaozorkow",
"vulcan.net.pl" to "gminalopiennikgorny",
)
@Test
fun loginTest() = runBlocking {
knownHosts.forEach { (host, symbol) ->
println("$host/$symbol")
val res = runCatching { getScrapper(host, symbol).getStudents() }
requireNotNull(res.exceptionOrNull()).cause!!.printStackTrace()
assert(res.exceptionOrNull() is BadCredentialsException)
println()
}
}
private fun getScrapper(domain: String, startSymbol: String): Scrapper = Scrapper().apply {
logLevel = HttpLoggingInterceptor.Level.BASIC
loginType = Scrapper.LoginType.AUTO
ssl = true
host = domain
symbol = startSymbol
email = "jan@fakelog.cf"
password = "jan123"
}
}

View file

@ -27,7 +27,7 @@ class LoginTest : BaseLocalTest() {
private val adfs by lazy {
LoginHelper(
loginType = Scrapper.LoginType.ADFSCards,
loginType = Scrapper.LoginType.ADFS,
schema = "http",
host = "fakelog.localhost:3000",
symbol = "default",
@ -39,7 +39,7 @@ class LoginTest : BaseLocalTest() {
okHttp = getOkHttp(
errorInterceptor = true,
autoLoginInterceptorOn = false,
loginType = Scrapper.LoginType.ADFSCards,
loginType = Scrapper.LoginType.ADFS,
),
),
)
@ -48,8 +48,6 @@ class LoginTest : BaseLocalTest() {
@Test
fun adfsTest() {
with(server) {
enqueue("ADFS-form-1.html")
enqueue("ADFS-form-2.html")
enqueue("Logowanie-cufs.html")
enqueue("Logowanie-uonet.html")
enqueue("Login-success.html")
@ -118,8 +116,6 @@ class LoginTest : BaseLocalTest() {
@Test
fun adfsBadCredentialsException() {
with(server) {
enqueue("ADFS-form-1.html")
enqueue("ADFS-form-2.html")
enqueue("Logowanie-adfs-zle-haslo.html")
start(3000)
}

View file

@ -117,9 +117,8 @@ class StudentStartRepositoryTest : BaseLocalTest() {
@Test
fun getSemesters_ADFS() {
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS-form-2.html").readText())) //
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS.html").readText())) //
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS-form-2.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Logowanie-cufs.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Logowanie-uonet.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Login-success.html").readText()))
@ -168,10 +167,9 @@ class StudentStartRepositoryTest : BaseLocalTest() {
@Test
fun getSemesters_ADFSCards() {
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS-form-1.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFSCards.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS-form-1.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFS-form-2.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("ADFSCards.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Logowanie-cufs.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Logowanie-uonet.html").readText()))
server.enqueue(MockResponse().setBody(LoginTest::class.java.getResource("Login-success.html").readText()))

View file

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Zaloguj</title>
</head>
<body dir="ltr" class="body">
<div id="noScript" style="position:static; width:100%; height:100%; z-index:100">
<h1>Wymagana obsługa języka JavaScript</h1>
<p>Obsługa języka JavaScript jest wymagana. Ta przeglądarka sieci Web nie obsługuje języka JavaScript lub obsługa języka JavaScript nie została w niej włączona.</p>
<p>Aby dowiedzieć się, czy przeglądarka sieci Web obsługuje język JavaScript, lub włączyć obsługę języka JavaScript, zapoznaj się z pomocą przeglądarki sieci Web.</p>
</div>
<div id="fullPage">
<div id="brandingWrapper" class="float">
<div id="branding"></div>
</div>
<div id="contentWrapper" class="float">
<div id="content">
<div id="header">
<img class='logoImage' id='companyLogo' src='/adfs/portal/logo/logo.png?id=6AE4C8BC3FFF3E443B8FE92A692DEC188DE80F9A253AF32B4EE7650537E42A9A' alt='GPE'/>
</div>
<div id="workArea">
<div id="authArea" class="groupMargin">
<div id="loginArea">
<div id="loginMessage" class="groupMargin">Zaloguj się przy użyciu konta organizacyjnego</div>
<form method="post" id="loginForm" autocomplete="off" novalidate="novalidate" onKeyPress="if (event && event.keyCode == 13) Login.submitLoginRequest();" action="/adfs/ls?wa=wsignin1.0&wtrealm=https%3a%2f%2fcufs.edu.gdansk.pl%3a443%2fgdansk%2fAccount%2fLogOn&wctx=rm%3d0%26id%3dadfs%26ru%3d%252fgdansk%252fFS%252fLS%253fwa%253dwsignin1.0%2526wtrealm%253dhttps%25253a%25252f%25252fuonetplus.edu.gdansk.pl%25252fgdansk%25252fLoginEndpoint.aspx%2526wctx%253dhttps%25253a%25252f%25252fuonetplus.edu.gdansk.pl%25252fgdansk%25252fLoginEndpoint.aspx&wct=2021-09-28T15%3a49%3a29Z&client-request-id=47465d7a-d0e3-4c3b-a272-0080010000db" >
<div id="error" class="fieldMargin error smallText">
<span id="errorText" for=""></span>
</div>
<div id="formsAuthenticationArea">
<div id="userNameArea">
<label id="userNameInputLabel" for="userNameInput" class="hidden">Konto użytkownika</label>
<input id="userNameInput" name="UserName" type="email" value="" tabindex="1" class="text fullWidth" spellcheck="false" placeholder="osoba@example.com" autocomplete="off"/>
</div>
<div id="passwordArea">
<label id="passwordInputLabel" for="passwordInput" class="hidden">Hasło</label>
<input id="passwordInput" name="Password" type="password" tabindex="2" class="text fullWidth" placeholder="Hasło" autocomplete="off"/>
</div>
<div id="kmsiArea" style="display:none">
<input type="checkbox" name="Kmsi" id="kmsiInput" value="true" tabindex="3" />
<label for="kmsiInput">Nie wylogowuj mnie</label>
</div>
<div id="submissionArea" class="submitMargin">
<span id="submitButton" class="submit" tabindex="4" role="button" onKeyPress="if (event && event.keyCode == 32) Login.submitLoginRequest();" onclick="return Login.submitLoginRequest();">Zaloguj</span>
</div>
</div>
<input id="optionForms" type="hidden" name="AuthMethod" value="FormsAuthentication"/>
</form>
<div id="authOptions">
<form id="options" method="post" action="https://adfs.edu.gdansk.pl:443/adfs/ls?wa=wsignin1.0&wtrealm=https%3a%2f%2fcufs.edu.gdansk.pl%3a443%2fgdansk%2fAccount%2fLogOn&wctx=rm%3d0%26id%3dadfs%26ru%3d%252fgdansk%252fFS%252fLS%253fwa%253dwsignin1.0%2526wtrealm%253dhttps%25253a%25252f%25252fuonetplus.edu.gdansk.pl%25252fgdansk%25252fLoginEndpoint.aspx%2526wctx%253dhttps%25253a%25252f%25252fuonetplus.edu.gdansk.pl%25252fgdansk%25252fLoginEndpoint.aspx&wct=2021-09-28T15%3a49%3a29Z&client-request-id=47465d7a-d0e3-4c3b-a272-0080010000db">
<input id="optionSelection" type="hidden" name="AuthMethod" />
<div id='authOptionLinks' class='groupMargin'></div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -5,26 +5,25 @@
<title>Logowanie do systemu</title>
</head>
<body>
<form action="/LoginPage.aspx" method="post">
<div class="mainDiv" id="mainDiv">
<table class="UsernamePasswordTable" id="tableUserCredentials">
<tr class="UsernamePasswordTableHeading">
<td colspan="2"><label>Proszę podać nazwę użytkownika i hasło</label></td>
</tr>
<tr>
<td class="UsernamePasswordTableField"><label for="Username">Nazwa użytkownika:</label></td>
<td><input id="Username" name="Username" type="text"/></td>
</tr>
<tr>
<td class="UsernamePasswordTableField"><label for="Password">Hasło:</label></td>
<td><input id="Password" name="Password" type="password"/></td>
</tr>
<tr>
<td colspan="2" class="SubmitTableField">
<input type="image" src="/Resources/g-zaloguj-1.png" id="SubmitButton"/>
</td>
</tr>
</table>
<form action="/symbol/LoginPage.aspx" method="post">
<div class="label">Zaloguj się</div>
<div class="box" id="box">
<div class="box-h">Proszę podać nazwę użytkownika i hasło</div>
<div class="box-p">
<label class="box-line" for="Username">Nazwa użytkownika:</label>
<input class="box-line" id="Username" name="Username" type="text"/>
</div>
<div class="box-p">
<label class="box-line" for="Password">Hasło:</label>
<input class="box-line" id="Password" name="Password" type="password"/>
</div>
<div class="box-p box-right">
<button type="submit" class="submit-button box-line">Zaloguj się</button>
</div>
<div>
<a class="box-line box-link" id="aUnlock" href="/symbol/AccountManage/UnlockAccountRequest"
title="Pierwsze logowanie lub odzyskiwanie hasła">Przywracanie dostępu do konta</a>
</div>
</div>
</form>
</body>