feat: add school authentication entry with guarded import
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
# 学校页面验证状态
|
||||
|
||||
当前没有登录后的研究生课表样本,也没有以人工构造表格冒充学校样本。
|
||||
|
||||
2026-10-12 已核对公开认证页面:密码表单位于 `.login-main #pwdFromId`,使用 username/passwordText 输入、saltPassword 隐藏加密字段、pwdEncryptSalt 与 startLogin/checkForm/encryptPassword 的学校原流程。自定义界面只填充此经过运行时检查的表单,并等待学校验证码检查结束后触发学校登录按钮。表单不匹配时回到学校页面。
|
||||
|
||||
实际账号认证、验证码、研究生 SSO 跳转与课表识别还需本人设备登录验证。无需在聊天中提供账号密码。SchoolPageAdapter 当前拒绝全部未验证布局,保持已导入的离线课表不变。
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hepan_everyday/features/school/school_navigation_policy.dart';
|
||||
|
||||
void main() {
|
||||
test('secure captcha frames are allowed without authorizing credential injection', () {
|
||||
final captcha = Uri.parse('https://captcha.example.test/challenge');
|
||||
expect(
|
||||
SchoolNavigationPolicy.canNavigate(captcha, isMainFrame: false),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
SchoolNavigationPolicy.canNavigate(captcha, isMainFrame: true),
|
||||
isFalse,
|
||||
);
|
||||
expect(SchoolNavigationPolicy.canInjectCredentials(captcha), isFalse);
|
||||
});
|
||||
test('credentials stay on the exact school HTTPS login endpoint', () {
|
||||
expect(
|
||||
SchoolNavigationPolicy.canInjectCredentials(
|
||||
Uri.parse('https://idas.uestc.edu.cn/authserver/login?service=school'),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
for (final value in [
|
||||
'http://idas.uestc.edu.cn/authserver/login',
|
||||
'https://idas.uestc.edu.cn.evil.test/authserver/login',
|
||||
'https://user@idas.uestc.edu.cn/authserver/login',
|
||||
'https://idas.uestc.edu.cn:444/authserver/login',
|
||||
'https://idas.uestc.edu.cn/other',
|
||||
]) {
|
||||
expect(
|
||||
SchoolNavigationPolicy.canInjectCredentials(Uri.parse(value)),
|
||||
isFalse,
|
||||
reason: value,
|
||||
);
|
||||
}
|
||||
});
|
||||
test('reading is limited to the supplied timetable path', () {
|
||||
expect(
|
||||
SchoolNavigationPolicy.isTimetable(
|
||||
Uri.parse('https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1'),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
SchoolNavigationPolicy.isTimetable(
|
||||
Uri.parse('https://yjsjy.uestc.edu.cn/pyxx/login'),
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
SchoolNavigationPolicy.isSchoolPage(Uri.parse('javascript:alert(1)')),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hepan_everyday/features/school/school_page_adapter.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'a rejected origin does not poison later legitimate page reading',
|
||||
() async {
|
||||
final reader = SchoolReader(SchoolPageAdapter());
|
||||
await expectLater(
|
||||
reader.read(
|
||||
Uri.parse('https://idas.uestc.edu.cn/authserver/login'),
|
||||
() async => 'x',
|
||||
),
|
||||
throwsA(isA<UnsupportedSchoolLayout>()),
|
||||
);
|
||||
var reads = 0;
|
||||
await expectLater(
|
||||
reader.read(
|
||||
Uri.parse('https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1'),
|
||||
() async {
|
||||
reads++;
|
||||
return 'unknown';
|
||||
},
|
||||
),
|
||||
throwsA(isA<UnsupportedSchoolLayout>()),
|
||||
);
|
||||
expect(reads, 1);
|
||||
},
|
||||
);
|
||||
test('concurrent timetable reading obtains page content once', () async {
|
||||
final reader = SchoolReader(SchoolPageAdapter());
|
||||
final body = Completer<String>();
|
||||
var reads = 0;
|
||||
Future<String> getHtml() {
|
||||
reads++;
|
||||
return body.future;
|
||||
}
|
||||
|
||||
final target = Uri.parse(
|
||||
'https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1',
|
||||
);
|
||||
final a = reader.read(target, getHtml), b = reader.read(target, getHtml);
|
||||
final checks = Future.wait([
|
||||
expectLater(a, throwsA(isA<UnsupportedSchoolLayout>())),
|
||||
expectLater(b, throwsA(isA<UnsupportedSchoolLayout>())),
|
||||
]);
|
||||
body.complete('unknown');
|
||||
await checks;
|
||||
expect(reads, 1);
|
||||
});
|
||||
test('unknown school markup never becomes an empty successful import', () {
|
||||
final adapter = SchoolPageAdapter();
|
||||
final target = Uri.parse(
|
||||
'https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1',
|
||||
);
|
||||
for (final html in [
|
||||
'',
|
||||
'<html>验证码</html>',
|
||||
'<table><tr><td>课程名称</td></tr></table>',
|
||||
]) {
|
||||
expect(
|
||||
() => adapter.extract(html, target),
|
||||
throwsA(isA<UnsupportedSchoolLayout>()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user