Skip to content

Commit

Permalink
Hardening (#137)
Browse files Browse the repository at this point in the history
* npe fix for userupdate call from dashboard

* fix for level tampering. added demotion to re-align users to belt levels. handling invalid levels more gracefully
  • Loading branch information
matallen authored Mar 15, 2021
1 parent 83cc999 commit 3a5c952
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/redhat/sso/ninja/ChatNotification.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @author mallen
*/
public class ChatNotification{
public enum ChatEvent{onRegistration,onBeltPromotion,onScriptError}
public enum ChatEvent{onRegistration,onBeltPromotion,onBeltDemotion,onScriptError,onSystemWarning}
// public static void main(String[] asd){
// Config c=Config.get();
// System.out.println("googlehangoutschat.webhook.template="+c.getOptions().get("googlehangoutschat.webhook.template"));
Expand Down
88 changes: 88 additions & 0 deletions src/main/java/com/redhat/sso/ninja/LevelUp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.redhat.sso.ninja;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;

import org.apache.log4j.Logger;

import com.redhat.sso.ninja.ChatNotification.ChatEvent;
import com.redhat.sso.ninja.utils.LevelsUtil;
import com.redhat.sso.ninja.utils.Tuple;

public class LevelUp{
private static final Logger log = Logger.getLogger(LevelUp.class);

public static void main(String[] args){
Map<String, String> userInfo=Database2.get().getUsers().get("<username>");
Map<String, Integer> scorecards=Database2.get().getScoreCards().get("<username>");
scorecards.put("testing", 18);

System.out.println("");
new LevelUp().levelUpChecks(Database2.get());

System.out.println("");
scorecards.clear();
new LevelUp().levelUpChecks(Database2.get());
}


public void levelUpChecks(Database2 db){
log.info("Level-up checks...");
int count=1;
Map<String, Map<String, String>> users=db.getUsers();
while (count>0){ // keep checking, some people may need multiple promotions in one go!
count=0;
for(Entry<String, Map<String, Integer>> e:db.getScoreCards().entrySet()){
String userId=e.getKey();
int total=0;
for(Entry<String, Integer> s:e.getValue().entrySet()){
total+=s.getValue();
}
Map<String, String> userInfo=users.get(userId);

Tuple<Integer, String> currentLevel=LevelsUtil.get().getLevel(userInfo.get("level"));
Tuple<Integer, String> nextLevel=LevelsUtil.get().getNextLevel(userInfo.get("level"));
Tuple<Integer, String> lastLevel=LevelsUtil.get().getLastLevel(userInfo.get("level"));

// Deserve a demotion?
if (total<=lastLevel.getLeft() && !currentLevel.getRight().equals(lastLevel.getRight())){
// Uh oh, the user doesnt have the points to be at the current level, shift them down...
log.info("User "+userId+" has been demoted to level "+lastLevel.getRight()+" with a points score of "+total);
userInfo.put("level", lastLevel.getRight());
userInfo.put("levelChanged", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));// nextLevel.getRight());

db.addEvent("User Demotion", userInfo.get("username"), "Demoted to "+lastLevel.getRight()+". Score is "+total);

String displayName=userInfo.containsKey("displayName")?userInfo.get("displayName"):userInfo.get("username");
new ChatNotification().send(ChatEvent.onBeltDemotion, displayName +" demoted to "+lastLevel.getRight());
count+=1;
}

// Deserve a promotion?
if (total>=nextLevel.getLeft() && !currentLevel.getRight().equals(nextLevel.getRight())){
// congrats! the user has been promoted!
log.info("User "+userId+" has been promoted to level "+nextLevel.getRight()+" with a points score of "+total);
userInfo.put("level", nextLevel.getRight());
userInfo.put("levelChanged", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));// nextLevel.getRight());

db.addEvent("User Promotion", userInfo.get("username"), "Promoted to "+nextLevel.getRight());

String displayName=userInfo.containsKey("displayName")?userInfo.get("displayName"):userInfo.get("username");
String message=displayName +" promoted to "+nextLevel.getRight();
db.addTask(message, userInfo.get("username"));

// Notify everyone on the Ninja chat group of a new belt promotion
new ChatNotification().send(ChatEvent.onBeltPromotion, message);

count+=1;
}

}
}
//db.save();
}

}
38 changes: 27 additions & 11 deletions src/main/java/com/redhat/sso/ninja/ManagementController.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,26 @@ public Response updateUserProperty(@Context HttpServletRequest request, @PathPar
Map<String, String> userInfo=db.getUsers().get(user);
if (null==userInfo) throw new JsonMappingException("User info for '"+user+"' not found");
for (Entry<String, String> e:values.entrySet()){
String existingValue=userInfo.get(e.getKey());

if (null==existingValue){ // no existing, value so just add it
userInfo.put(e.getKey(), e.getValue());
db.addEvent("User Update", user, e.getKey()+" added as "+e.getValue());
}else{ // value exists, so update it (if it's changed)
if (!existingValue.equals(e.getValue())){
String existingValue=userInfo.get(e.getKey());
if ("displayName".equals(e.getKey()) || e.getKey().endsWith("Id")){ // ensure we only update info fields, not points or levels

if (null==existingValue){ // no existing, value so just add it
userInfo.put(e.getKey(), e.getValue());
// Add an event entry so we know what was changed and when
db.addEvent("User Update", user, e.getKey()+" changed from "+existingValue+" to "+e.getValue());
db.addEvent("User Update", user, e.getKey()+" added as "+e.getValue());
}else{ // value exists, so update it (if it's changed)
if (!existingValue.equals(e.getValue())){
userInfo.put(e.getKey(), e.getValue());
// Add an event entry so we know what was changed and when
db.addEvent("User Update", user, e.getKey()+" changed from "+existingValue+" to "+e.getValue());
}
}

}else{
// Field NOT ALLOWED - potential fraudulent activity
if (e.getKey().contains("level")){
log.warn("Suspicious Activity: User ["+user+"] attempting to update their level from ["+existingValue+"] to ["+e.getValue()+"]");
new ChatNotification().send(ChatNotification.ChatEvent.onSystemWarning, "Suspicious Activity: User "+user+" has attempted to update their level via the API from "+existingValue+" to "+e.getValue()+"");
}
}

Expand Down Expand Up @@ -523,9 +533,15 @@ public Response getScorecards() throws JsonGenerationException, JsonMappingExcep
}

// points to next level
Integer pointsToNextLevel=LevelsUtil.get().getNextLevel(userInfo.get("level")).getLeft()-total;
if (pointsToNextLevel<0) pointsToNextLevel=0;
row.put("pointsToNextLevel", pointsToNextLevel);
if (null==userInfo.get("level") || null==LevelsUtil.get().getNextLevel(userInfo.get("level"))){
log.error("Invalid level for user "+row.get("name")+" : "+Json.newObjectMapper(true).writeValueAsString(userInfo));
row.put("pointsToNextLevel", 0);
}else{
Integer pointsToNextLevel=LevelsUtil.get().getNextLevel(userInfo.get("level")).getLeft()-total;
if (pointsToNextLevel<0) pointsToNextLevel=0;
row.put("pointsToNextLevel", pointsToNextLevel);
}

data.add(row);
}

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/redhat/sso/ninja/utils/LevelsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public Tuple<Integer,String> getNextLevel(String currentLevelName){
}
return null; // should never happen
}
public Tuple<Integer,String> getLastLevel(String currentLevelName){
if (levels.get(0).getRight().equals(currentLevelName)) return levels.get(0);
for(int i=levels.size()-1;i>=0;i--){
if (levels.get(i).getRight().equals(currentLevelName)) return levels.get((i-1)==levels.size()?i:i-1);
}
return null; // should never happen
}

//logic: last one you hit where the points are greater than required for the level
public Tuple<Integer,String> getLevelGivenPoints(Integer points){
Expand Down

0 comments on commit 3a5c952

Please sign in to comment.