Saturday, December 12, 2015

Angular Charts and the "undefined" draw error on single series line charts

I've been recently working with Ionic and my app needed a chart. Also, been listening to way too much Tricot and Toe.

This chart requirement lead me to angular-chart which is based on the excellent chart.js library. My use case was simple. I needed a line chart to visualize various "single" series data. And this is where I encountered this "undefined" draw (and update) functions.

For context, this how my data looked.

{
   id: 0,
   labels: ['13:00', '13:15', '13:30', '13:45', '14:00', '14:15', '14:30', '14:45'],
   series: ['Stat #1'],
   data: [28, 30, 29, 32, 70, 79, 89, 98 ]
}

The important part here is the data array. This was the one causing the problem after I traced it to a closed issue in github. To fix this was to just turn the flat data array into a 2-dimensional array and poof the error is gone.

{
   id: 0,
   labels: ['13:00', '13:15', '13:30', '13:45', '14:00', '14:15', '14:30', '14:45'],
   series: ['Stat #1'],
   data: [[28, 30, 29, 32, 70, 79, 89, 98 ]]
}

I can now see my chart.

Wednesday, December 2, 2015

A tutorial on how to use the Tapestry-Security module Part 2

We pick up where we left off.

We start with our AppModule and configure our security service to use our "custom" realm.

    
    // @see http://tynamo.org/tapestry-security+guide
    @Contribute(WebSecurityManager.class)
    public static void addRealms(Configuration configuration, @InjectService("MiscAppRealm") AuthorizingRealm userRealm) {
        configuration.add(userRealm);
    }

With this we then update our MiscRealm.java code:
    
  @Inject
  private UserMembershipDAO umDAO;    

  public MiscAppRealm() {
        super(new MemoryConstrainedCacheManager());
        setAuthenticationTokenClass(UsernamePasswordToken.class);
        setCredentialsMatcher(new MiscRealmCredentialMatcher());
  }

  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        if(pc == null) throw new AuthenticationException("PrincipalCollection was null, which should not happen");
        
        if(pc.isEmpty()) return null;
        
        if(pc.fromRealm(getName()).size() <= 0) return null;
        
        String loginName = (String)pc.fromRealm(getName()).iterator().next();
        if(loginName == null) return null;
                
        AppUsers user = umDAO.FindAppUsersByLoginId(loginName);
        
        if(user == null) return null;
                
        Set roles = new HashSet<>(user.getRolesList().size());
        user.getRolesList().stream().forEach((role) -> {
            roles.add(role.getDescription());
        });
        
        System.out.println("Roles: " + roles);
        
        return new SimpleAuthorizationInfo(roles);
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {                
        UsernamePasswordToken token = (UsernamePasswordToken)at;
        
        String username = token.getUsername();
        
        if(username == null){
            throw new AccountException("Null usernames are not allowed by this realm");
        }
        
        AppUsers user = umDAO.FindAppUsersByLoginId(username);
                
        if(user.getIsLocked()){
            throw new LockedAccountException("Account: " + username + " is locked.");
        }
         
        return new SimpleAuthenticationInfo(username, user.getEncodedPassword(), new SimpleByteSource(user.getPasswordSalt()), getName());
 }

We then move on to our custom credential matcher (MiscRealmCredentialMatcher.java) which extends from SimpleCredentialsMatcher. The thing there is overriding the doCredentialsMatch() method on it. You need to remember though is that the UserMembership table stores the encrypted password instead as plain text. You'll get the idea when you see the jasypt documentation.

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
     
     AppUsers target = FindAppUsersByLoginId(UserId);
     String encryptedPassword = target.getPassword();
        
     BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
        
     return passwordEncryptor.checkPassword(String.valueOf(token.getPrincipal()), encryptedPassword);
}

The last part of the puzzle is how we handle the Login form when we submit it. The Tynamo-Security's own code is helpful here.

    
void onValidateFromLogin() throws ValidationException {
        
     Subject currentUser = securityService.getSubject();
                
     if(currentUser == null){
         throw new IllegalStateException("Subject can`t be null");
     }
        
     UsernamePasswordToken token = new UsernamePasswordToken(loginId, password);
     token.setRememberMe(rememberMe);
        
     try {
          currentUser.login(token);
            
     } catch (UnknownAccountException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     } catch (IncorrectCredentialsException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     } catch (LockedAccountException e) {
         login.recordError(loginIdField, "Account is Locked. Please see Administrator");
     } catch (AuthenticationException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     }
}

Object onSuccessFromLogin() throws MalformedURLException, IOException {
    if (StringUtils.hasText(successURL)) {
        if ("^".equals(successURL)) {
             return pageRenderLinkSource.createPageRenderLink(componentResources.getPage().getClass());
        }
        return new URL(successURL);
    }

    if (redirectToSavedUrl) {
         String requestUri = loginContextService.getSuccessPage();
         if (!requestUri.startsWith("/") && !requestUri.startsWith("http")) {
             requestUri = "/" + requestUri;
         }
         loginContextService.redirectToSavedRequest(requestUri);
        return null;
    }
    return loginContextService.getSuccessPage();
}

What's left is to annotate the pages to secure.

Edit: smallish changes to code