续接上文
本文介绍如果修改 authz 文件和 htpasswd 文件
一、authz 修改
用户授权,主要是在authz里下[groups]给指定角色添加一个用户
这里造了点简单的数据
[/]
*=r
@SVN_ADMIN=rw
[groups]
SVN_ADNIN=admin
PRJ_SHU-PM=liubei
PRJ_SHU-CCB=guanyu,zhugeliang
PRJ_SHU-DEV=huangzhong,zhaoyun
PRJ_SHU-REQ=jiangwei
PRJ_SHU-TEST=weiyan
[项目:/]
*=r
@SVN_ADNIN=rw
[项目:/蜀汉项目]
*=
@SVN_ADMIN=rw
PRJ_SHU-PM=rw
PRJ_SHU-CCB=r
PRJ_SHU-DEV=r
PRJ_SHU-TEST=r
[项目:/蜀汉项目/开发库/01.项目管理]
PRJ_SHU-CCB=rw
[项目:/蜀汉项目/开发库/02.需求]
PRJ_SHU-CCB=rw
PRJ_SHU-REQ=rw
PRJ_SHU-DEV=
PRJ_SHU-TEST=
[项目:/蜀汉项目/开发库/03.设计与编码]
PRJ_SHU-DEV=rw
[项目:/蜀汉项目/开发库/04.测试]
PRJ_SHU-TEST=rw
如果我们需要给指定角色添加一个用户,如 给 PRJ_SHU-TEST添加一个 liyan
只需要按行读取 authz 文件匹配到 startWith 为 PRJ_SHU-TEST 的数据,然后替换为 PRJ_SHU-TEST=weiyan,liyan
因为 authz 文件比较大,如果项目多的话,可能会有几MB或几十MB,读取和替换很慢
解决办法是采用随机读的方法,即 RandomAccessFile,如何替换单行语句,具体我下面会介绍
二、htpasswd 修改
这里造了点数据
admin:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
guanyu:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
zhugeliang:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
huangzhong:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
Visual SVN密码是进行加密的,加密算法是:Md5Crypt.apr1Crypt(),即MD5加盐加密,密码不可解密
在页面上修改密码,进行上面加密后,然后写入到htpasswd文件中,可以先判断用户名是否存在,如果存在则替换,否则在末尾查询。替换的方法见下文中随机读替换
三、RandomAccessFile 随机读替换单行
1、MatchPatternEnum
/**
* 关键字匹配枚举
*/
public enum MatchPatternEnum {
/**
* equals 全匹配、包含、正则匹配规则、开始
*/
EQUALS("equals"),CONTAIN("contains"),REGEX("regex"), STARTS_WITH("starts_with");
private String desc;
MatchPatternEnum(String desc){
this.desc = desc;
}
public static MatchPatternEnum getEnumByDesc(String desc){
for (MatchPatternEnum patternEnum : MatchPatternEnum.values())
{
if (patternEnum.desc.equals(desc))
{
return patternEnum;
}
}
return null;
}
public String getDesc()
{
return desc;
}
public void setDesc(String desc)
{
this.desc = desc;
}
}
2、PathAuth
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 路径权限对象
*/
public class PathAuth extends RandomIO implements IRandomObj {
/**
* 路径、搜索关键词,如 [项目:/蜀汉项目] 或 liubei=
*/
private String keyword;
/**
* 替换内容
*/
private String content;
/**
* 匹配模式( 全匹配、模糊、正则)
*/
private MatchPatternEnum matchPattern;
/**
* 结束符号
*/
private String finishSign;
public PathAuth(){
//默认全匹配
this.matchPattern = MatchPatternEnum.EQUALS;
}
@Override
public String getKeyWord() {
return this.keyword;
}
@Override
public boolean replaceOneLine(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception {
Map<String, Long> posMap = searchPos(this, oraf);
if (posMap != null && posMap.size() > 0){
Long startPos = posMap.get("startPos");
Long endPos = posMap.get("endPos");
long delLength = endPos - startPos;
StringBuilder holdContent = getHoldContent(oraf, endPos);
oraf.seek(startPos);
//新权限行内容-单行
String oneLineAuth = this.getContent();
StringBuilder replaceContent = new StringBuilder();
replaceContent.append(oneLineAuth).append("\r\n");
writeString(replaceContent.toString(),oraf);
oraf.seek(startPos);
oraf.readLine();
long newEndPos = oraf.getFilePointer();
long newDelLength = newEndPos - startPos;
long needDel = delLength - newDelLength;
oraf.write(holdContent.toString().getBytes(StandardCharsets.UTF_8));
if( needDel > 0 ){
long length = oraf.length();
oraf.setLength(length-needDel);
}
}
if( needClose ) {
oraf.close();
}
return true;
}
@Override
public boolean insertBulk(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception {
Map<String, Long> posMap = searchPos(this, oraf);
if (posMap != null && posMap.size() > 0){
Long endPos = posMap.get("endPos");
StringBuilder holdContent = getHoldContent(oraf, endPos);
//将匹配到的内容写入进文件中去
String insertContent = this.getContent();
//替换内容中包含\r\n
String[] split = insertContent.split("\\\\r\\\\n");
StringBuilder insertContentBuild = new StringBuilder();
for (String s : split) {
insertContentBuild.append(s).append("\r\n");
}
oraf.seek(endPos);
oraf.write(insertContentBuild.toString().getBytes(StandardCharsets.UTF_8));
oraf.write(holdContent.toString().getBytes(StandardCharsets.UTF_8));
}
if( needClose ) {
oraf.close();
}
return true;
}
public String getKeyword()
{
return keyword;
}
public void setKeyword(String keyword)
{
this.keyword = keyword;
}
public String getContent()
{
return content;
}
public void setContent(String content)
{
this.content = content;
}
@Override
public MatchPatternEnum getMatchPattern()
{
return matchPattern;
}
public void setMatchPattern(MatchPatternEnum matchPattern)
{
this.matchPattern = matchPattern;
}
@Override
public String getFinishSign()
{
return finishSign;
}
public void setFinishSign(String finishSign)
{
this.finishSign = finishSign;
}
}
3、IRandomObj
/**
* 随机读取对象接口定义
*/
public interface IRandomObj {
/**
* 获取关键数据
* @return
*/
String getKeyWord();
/**
* 获取关键数据匹配规则
* @return
*/
Enum getMatchPattern();
/**
* 获取关键数据替换结束符
* @return
*/
String getFinishSign();
/**
* 替换指定行
* @param oraf
* @param needClose 是否需要关闭流
* @return
* @throws Exception
*/
boolean replaceOneLine(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception;
/**
* 在指定字符下面插入内容
* @param oraf
* @param needClose
* @return
* @throws Exception
*/
boolean insertBulk(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception;
}
4、RandomIO
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 随机读写类
*
* @author linzhuochi
*/
public class RandomIO
{
/**
* 写入文件
*
* @param s
* @param oraf
* @throws IOException
*/
public void writeString(String s, OptimizedRandomAccessFile oraf) throws IOException
{
oraf.write(s.getBytes(StandardCharsets.UTF_8));
}
/**
* 搜索关键字指定索引(关键字前、后位置)
*
* @param randomObj
* @param oraf
* @return Map<String, Long> START_POS END_POS
* @throws IOException
*/
public Map<String, Long> searchPos(IRandomObj randomObj, OptimizedRandomAccessFile oraf) throws Exception
{
String keyWord = randomObj.getKeyWord();
String currentLine;
long startPos = 0;
long endPos = -1;
long replaceEndPos = -1;
Map<String, Long> result = null;
Enum matchPattern = randomObj.getMatchPattern();
String finishSign = randomObj.getFinishSign();
boolean equals = MatchPatternEnum.EQUALS.equals(matchPattern);
boolean contains = MatchPatternEnum.CONTAIN.equals(matchPattern);
boolean regex = MatchPatternEnum.REGEX.equals(matchPattern);
boolean startsWith = MatchPatternEnum.STARTS_WITH.equals(matchPattern);
boolean findFlag = false;
while ((currentLine = oraf.readLine()) != null)
{
currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
endPos = oraf.getFilePointer();
if (regex && currentLine.matches(keyWord) || equals && currentLine.equals(keyWord)
|| contains && currentLine.contains(keyWord) || startsWith && currentLine.startsWith(keyWord))
{
findFlag = true;
break;
}
startPos = oraf.getFilePointer();
}
if (findFlag)
{
result = new HashMap<>();
result.put("startPos", startPos);
result.put("endPos", endPos);
}
if (findFlag && finishSign != null && !"".equals(finishSign))
{
replaceEndPos = endPos;
while ((currentLine = oraf.readLine()) != null)
{
currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
if (currentLine.contains(finishSign))
{
break;
}
replaceEndPos = oraf.getFilePointer();
}
result.put("replaceEndPos", replaceEndPos);
}
return result;
}
/**
* 保存暂存数据
*
* @param oraf
* @return
* @throws IOException
*/
public StringBuilder getHoldContent(OptimizedRandomAccessFile oraf, long holdStartPos) throws IOException
{
oraf.seek(holdStartPos);
String currentLine;
StringBuilder holdContent = new StringBuilder();
;
while ((currentLine = oraf.readLine()) != null)
{
currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
holdContent.append(currentLine).append("\r\n");
}
return holdContent;
}
}
5、OptimizedRandomAccessFile
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
/**
* 优化版本随机读
*/
public class OptimizedRandomAccessFile {
private static final int BUFFER_SIZE = 8192;
private static int defaultExpectedLineLength = 80;
private RandomAccessFile raf;
private Long actualFilePointer;
private byte[] charBuffer;
private int nChars, nextChar;
private int bufferSize;
private long lastOffset;
private boolean skipLF;
/**
* see {@link RandomAccessFile#RandomAccessFile(String,String)}
*
* @param name path to the text file
* @param mode r, rw, rws, rwd
* @throws FileNotFoundException
*/
public OptimizedRandomAccessFile(String name, String mode)
throws FileNotFoundException {
this(name != null ? new File(name) : null, mode);
}
/**
*
* see {@link RandomAccessFile#RandomAccessFile(File,String)}
*
* @param file
* @param mode
* @throws FileNotFoundException
*/
public OptimizedRandomAccessFile(File file, String mode)
throws FileNotFoundException {
this.raf = new RandomAccessFile(file, mode);
actualFilePointer = null;
this.bufferSize = BUFFER_SIZE;
charBuffer = new byte[bufferSize];
}
/**
* Writes
* <code>b.length</code> bytes from the specified byte array to this file,
* starting at the current file pointer.
*
* @param b the data.
* @exception IOException if an I/O error occurs.
*/
public synchronized void write(byte b[]) throws IOException {
resetPosition();
raf.write(b, 0, b.length);
}
// 'Random access' stuff
/**
* Returns the current offset in this file.
*
* @return the offset from the beginning of the file, in bytes, at which the
* next read or write occurs.
* @exception IOException if an I/O error occurs.
*/
public synchronized long getFilePointer() throws IOException {
if (actualFilePointer == null) {
return raf.getFilePointer();
} else {
return this.actualFilePointer;
}
}
/**
* Sets the file-pointer offset, measured from the beginning of this file,
* at which the next read or write occurs. The offset may be set beyond the
* end of the file. Setting the offset beyond the end of the file does not
* change the file length. The file length will change only by writing after
* the offset has been set beyond the end of the file.
*
* @param pos the offset position, measured in bytes from the beginning of
* the file, at which to set the file pointer.
* @exception IOException if <code>pos</code> is less than <code>0</code> or
* if an I/O error occurs.
*/
public synchronized void seek(long pos) throws IOException {
actualFilePointer = null;
resetPosition();
raf.seek(pos);
}
/**
* Returns the length of this file.
*
* @return the length of this file, measured in bytes.
* @exception IOException if an I/O error occurs.
*/
public synchronized long length() throws IOException {
return raf.length();
}
/**
* Sets the length of this file.
*
* <p> If the present length of the file as returned by the
* <code>length</code> method is greater than the
* <code>newLength</code> argument then the file will be truncated. In this
* case, if the file offset as returned by the
* <code>getFilePointer</code> method is greater than
* <code>newLength</code> then after this method returns the offset will be
* equal to
* <code>newLength</code>.
*
* <p> If the present length of the file as returned by the
* <code>length</code> method is smaller than the
* <code>newLength</code> argument then the file will be extended. In this
* case, the contents of the extended portion of the file are not defined.
*
* @param newLength The desired length of the file
* @exception IOException If an I/O error occurs
* @since 1.2
*/
public synchronized void setLength(long newLength) throws IOException {
if (newLength < raf.length()) {
resetPosition();
}
raf.setLength(newLength);
}
/**
* Closes this random access file stream and releases any system resources
* associated with the stream. A closed random access file cannot perform
* input or output operations and cannot be reopened.
*
* <p> If this file has an associated channel then the channel is closed as
* well.
*
* @exception IOException if an I/O error occurs.
*
* @revised 1.4
* @spec JSR-51
*/
public void close() throws IOException {
raf.close();
}
/**
* <p> Read the file line by line omitting the line separator. </p> <p> see
* {@link RandomAccessFile#readLine() readLine()} and see
* {@link java.io.BufferedReader#readLine(boolean) readLine(boolean ignoreLF)}.
* <p>
*
* <p> Subsequent calls of this method are buffered. If certain other
* methods that are affected by the current position of the reader in the
* file is called after this method, the position is set to the start of the
* next line and the buffer is invalidated. </p>
*
* <p> This method is copied from
* {@link java.io.BufferedReader BufferedReader} with minor changes like
* tracking position (offset) were next line starts. </p>
*
* @return the next line of text from this file, or null if end of file is
* encountered before even one byte is read.
* @exception IOException if an I/O error occurs.
*/
public synchronized final String readLine(boolean ignoreLF) throws IOException {
StringBuilder s = null;
int startChar;
int separatorIndex = 0;
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
if (nextChar >= nChars) {
fill();
}
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0) {
//EOF -> hence no need to adjust position in file
// changed by fill()
return s.toString();
} else {
return null;
}
}
boolean eol = false;
byte c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && ((char) charBuffer[nextChar] == '\n')) {
nextChar++;
}
skipLF = false;
omitLF = false;
charLoop:
for (i = nextChar; i < nChars; i++) {
c = charBuffer[i];
if (((char) c == '\n') || ((char) c == '\r')) {
eol = true;
break charLoop;
}
}
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
if (s == null) {
str = new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1);
} else {
s.append(new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1));
str = s.toString();
}
nextChar++;
if ((char) c == '\r') {
skipLF = true;
if (nextChar >= nChars) {
fill();
}
if ((char) charBuffer[nextChar] == '\n') {
separatorIndex = 1;
}
}
actualFilePointer = lastOffset + nextChar + separatorIndex;
return str;
}
if (s == null) {
s = new StringBuilder(defaultExpectedLineLength);
}
s.append(new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1)); // WORKS, ugly!
}
}
/**
* see {@link #readLine(boolean) readLine(boolean ignoreLF)}
*
* @return
* @throws IOException
*/
public synchronized String readLine() throws IOException {
return readLine(false);
}
private void fill() throws IOException {
lastOffset = raf.getFilePointer();
actualFilePointer = lastOffset;
byte[] buffer = new byte[bufferSize];
int n = raf.read(buffer);
if (n > 0) {
nChars = n;
nextChar = 0;
}
for (int i = 0; i < buffer.length; i++) {
charBuffer[i] = buffer[i];
}
}
private void resetPosition() throws IOException {
if (actualFilePointer != null) {
raf.seek(actualFilePointer);
actualFilePointer = null;
}
nChars = 0;
nextChar = 0;
}
}
6、测试类
/**
* 测试类
* @author liuyanzhao
* @date 2021/12/20 15:16
*/
public class Main
{
public static void main(String[] args) throws Exception
{
// 测试在 htpasswd 里替换 liubei 为 liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
OptimizedRandomAccessFile optimizedRandomAccessFile = new OptimizedRandomAccessFile("F:\\VisualSVN\\Repositories\\htpasswd", "rw");
PathAuth pathAuth = new PathAuth();
pathAuth.setMatchPattern(MatchPatternEnum.STARTS_WITH);
pathAuth.setKeyword("liubei");
pathAuth.setContent("liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.");
pathAuth.replaceOneLine(optimizedRandomAccessFile, true);
// 测试在 authz 在 [groups] 插入 内容块
OptimizedRandomAccessFile optimizedRandomAccessFile2 = new OptimizedRandomAccessFile("F:\\VisualSVN\\Repositories\\authz", "rw");
PathAuth pathAuth2 = new PathAuth();
pathAuth2.setMatchPattern(MatchPatternEnum.STARTS_WITH);
pathAuth2.setKeyword("[groups]");
pathAuth2.setContent("PRJ_WEI-PM=caocao\r\n" +
"PRJ_WEI-CCB=xiahoudun,caoren\r\n" +
"PRJ_WEI-DEV=yejin,dianwei,juyin\r\n" +
"PRJ_WEI-REQ=zhangliao\r\n" +
"PRJ_WEI-TEST=guojia\r\n");
pathAuth2.insertBulk(optimizedRandomAccessFile2, true);
}
}
您可以选择一种方式赞助本站
支付宝扫一扫赞助
微信钱包扫描赞助
赏