Un crud avec connexion, Coldfusion et Coldbox

Un crud avec connexion, Coldfusion et Coldbox

 

 

testcolbox

Dans ce petit exo, je reprend tout le code du précédent exercice Coldfusion pour réaliser un CRUD tout simple, et j’y ajoute un espace de connexion, deconnexion.

On installe Coldfusion, Coldbox en suivant les précédents tutos, et on crée un projet.Je l’appelle ici aussi ‘Contacts’.Je crée une base de donnée vide dans PhpMyAdmin, nommée contacts aussi pour l’exemple, et je crée une datasource ‘contacts’ dans l’administration de ColdFusion.On oublie pas de créer un mapping vers Coldbox,toujours dans l’admin de Coldfu,  afin que notre appli sache ou chercher toutes les dependances.

Le but ici, est d’avoir une appli, un première page de connexion qui nous permet d’accéder au CRUD, pour pouvoir ensuite éditer, supprimer ou ajouter des contacts.Une fois deconnecté, on revient à la page de connexion

Mon arbo est la suivante:

arbo

Voici un à un les fichiers à éditer:

1-Coldbox.cfc

component {

// Configure ColdBox Application
function configure(){

// coldbox directives
coldbox = {
//Application Setup
appName = "contacts",

//Development Settings
debugMode = true,
debugPassword = "",
reinitPassword = "",
handlersIndexAutoReload = true,

//Implicit Events
defaultEvent = "contacts.index",

//Error/Exception Handling
customErrorTemplate = "/coldbox/system/includes/BugReport.cfm",

//Application Aspects
handlerCaching = false,
eventCaching = false
};

// Custom Settings
settings = {
};

orm = {
// entity injection
injection = {
// enable it
enabled = true,
// the include list for injection
include = "",
// the exclude list for injection
exclude = ""
}
};
// environment settings, create a detectEnvironment() method to detect it yourself.
// create a function with the name of the environment so it can be executed if that environment is detected
// the value of the environment is a list of regex patterns to match the cgi.http_host.
/*environments = {
development = "^localhost,^cf,^railo"
};*/

//Layout Settings
layoutSettings = {
defaultLayout = "Layout.main.cfm"
};

//Interceptor Settings
//Register interceptors as an array, we need order
interceptors = [
//Autowire
{class="model.securityInterceptor"}
];

//LogBox DSL
/*logBox = {
// Define Appenders
appenders = {
coldboxTracer = { class="coldbox.system.logging.appenders.ColdboxTracerAppender" },
coldboxFile = {
class="coldbox.system.logging.appenders.AsyncRollingFileAppender",
properties={filePath="logs",
fileName=coldbox.appname,
autoExpand=true,
fileMaxSize=2000,
fileMaxArchives=2}

}
},
// Root Logger
root = { levelmax="INFO", appenders="*" },
// Implicit Level Categories
info = [ "coldbox.system" ]
};*/
datasources = {
myDSN = {name="newcontacts",dbtype="mysql"}
};


}

function development(){
coldbox.debugPassword = "";
coldbox.debugMode = true;
coldbox.reinitPassword = "";
coldbox.handlerCaching = false;
coldbox.handlersIndexAutoReload = true;
coldbox.eventCaching = true;
}

}

Remarquez la déclaration d’un interceptor, c’est lui qui va obliger l’utilisateur à se connecter, avant d’arriver à l’application.

2-Application.cfc: j’active l’ORM

component extends="coldbox.system.Coldbox" {
this.name = hash(getCurrentTemplatePath());
this.sessionManagement = true;
this.sessionTimeout = createTimeSpan(0,0,30,0);
this.setClientCookies = true;

COLDBOX_APP_ROOT_PATH = getDirectoryFromPath(getCurrentTemplatePath());
COLDBOX_APP_MAPPING = "";
COLDBOX_CONFIG_FILE = "";
COLDBOX_APP_KEY = "";

// ORM Settings
this.ormEnabled = true;
this.ormSettings = {
datasource = "newcontacts",
cfclocation = "model",
dialect="MySQL",
autorebuild=true,
dbcreate = "update",
logSQL = true,
flushAtRequestEnd = false,
autoManageSession = false,
eventHandling = true
/*eventHandler = "model.MyEventHandler"*/
};

/*
function onApplicationStart(){

loadColdBox();
return true;
}

function onRequestStart( required string targetPage ) {

// ORM Reload Check
if( structKeyExists( url, "ormreload" ) ) {
ORMReload();
}

reloadChecks();

// Process A ColdBox Request Only
if( findNoCase( 'index.cfm', listLast( arguments.targetPage, '/' ) ) ) {
processColdBoxRequest();
}

return true;
}

function onApplicationEnd(){

}
function onSessionStart(){

super.onSessionStart();
}

function onSessionEnd(){
super.onSessionEnd(argumentCollection=arguments);
}


*/



}

 

3-Handlers : notre controller Contacts.cfc

/**
* I am a new handler
*/
component {



function index(event,rc,prc){
prc.contacts = EntityLoad("Contact", {}, "name");
event.setView("contacts/index");
}

function editor(event,rc,prc){
if(structKeyExists(rc, "contactID") && len(rc.contactID)){
prc.contacts = entityLoad("Contact", rc.contactID, true);
}else{
prc.contacts = entityNew("Contact");
}
event.setView("contacts/editor");
}

function delete(event,rc,prc){
rc.oContact= entityLoad('Contact',rc.contactID,true);
entityDelete(rc.oContact);
ORMflush();

getPlugin("MessageBox").info("Contact Removed!");
setNextEvent("contacts.index");
}

function save(event,rc,prc){
try{
if(structKeyExists(rc, "contactID") && len(rc.contactID)){
oContact = entityLoad("Contact", rc.contactID, true);
}else{
oContact = entityNew("Contact");
}
populateModel( oContact );
entitySave(oContact);
ORMflush();
getPlugin("MessageBox").info("Contact Created!");
setNextEvent("contacts.index");
}catch(any e){
getPlugin("MessageBox").error(e.message);
return editor(event,rc,prc);
}
}

/*function setMyservice(MyService) inject="SecurityService"{
variables.SecurityService = arguments.SecurityService;
}*/
// login form
function login(event,rc,prc){
event.setView("contacts/login");
}

// do login
function doLogin(event,rc,prc){

//Do Login Procedure.
var objDataStore = "";

var ValidationStruct = "";
//Error checks, does the form variables username & password exist
//in the request collection? if they do, are they blank?
if( not Event.valueExists("name") or not Event.valueExists("password") ){
//Set a message to display
getPlugin("MessageBox").setMessage("error","No username or password defined.");
getPlugin("Logger").logEntry("error","Login without variables set detected.");
//Redirect to next event, you can also add extra parameters to the URL
setNextEvent("Contacts.Login","name=#Event.getValue("name","")#");
}
else{
//Init DataStorage
objDataStore = CreateObject("component","#getSetting("AppMapping")#.model.datastore").init();
ValidationStruct = objDataStore.validateUser(rc.name, rc.password);
if ( ValidationStruct.validated ){
//Login Correct.
//set my session vars
getPlugin("SessionStorage").setVar("loggedin",true);
getPlugin("SessionStorage").setVar("name",ValidationStruct.qUser.name);
//Log the entry
getPlugin("Logger").logEntry("information","l'utilisateur' #validationStruct.qUser.name# es connecte.");
//relocate to home page.
setNextEvent("Contacts.Index");
}
else{
//Set a message to display
getPlugin("MessageBox").setMessage("error","Erreur.Recommencez");
getPlugin("Logger").logEntry("warning","Invalid logon information detected. IP used: #cgi.remote_addr#");
//Redirect to next event, you can also add extra parameters to the URL
setNextEvent("Contacts.Login","name=#Event.getValue("name")#");
}
}
}

function authenticate(required name, required password){
// hash password
arguments.password = hash( arguments.password, userService.getHashType() );
var user = userService.findWhere({name=arguments.name,password=arguments.password,isActive=true});

//check if found and return verification
if( not isNull(user) ){
// Set last login date
updateUserLoginTimestamp( user );
// set them in session
setUserSession( user );
return true;
}
return false;
}
// logout
function doLogout(event,rc,prc){
/*prc.contacts.deleteUserSession();*/
getPlugin("SessionStorage").clearAll();
setNextEvent("Contacts.Login");
}

}

Par rapport au simple CRUD, ici je crée les fonctions de connexion et deconnection, en faisant appel notamment au plugin session storage.

 

4-Style.css

Un peu de style pour commencer:

body {
font: 10pt verdana;
font-family: verdana;
padding: 0;
margin: 10px;
}


td {
padding: 10px;
margin: 0;
}
h2 {
background-color: black;
margin: 0px;
color: #FFFFFF;
padding: 20px;
}

#container{max-width:968px;margin:0 auto;margin:auto;}
.main{display:table-row}
.content{display:table-cell;min-width: 600px;width:100%;background:#eee}
.aside{display:table-cell; background: red;}
ul, li{list-style-type:none;display:inline-block;margin:0 auto;text-align:center}
ul li{min-width:120px;height:30px;}
ul li a{color:#fff;font-size:16px;line-height:30px;text-decoration:none}
ul li:hover{background:#333}
.menu_nav{background:#444; width:100%}
footer{line-height:30px;text-align:center;width:100%;background:#555}
.contact{max-width:300px;display:inline-block;padding:5px;text-align:left;margin:10px;border:1px solid #eee;background:#444}

5-Layout.Main.cfm

<cfset sessionstorage = getPlugin("SessionStorage")>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Welcome to SimpleCrud!!</title>
<link rel="stylesheet" href="includes/styles/style.css" type="text/css">
</head>
<body>
<!-- wrapper -->
<div class="clr" id="top-head"> </div>
<div id="container">
<!--header -->
<div id="header" >
<div class="logo-bg" >
<!--logo -->
<div class="logo">
LOGO
</div>
<!--head right -->
<div class="right">
<!--// Navigation //-->
<div style="font-size:14px">
<strong>
<cfif sessionstorage.getVar("loggedin",false)>
<cfoutput> Bienvenue #sessionstorage.getVar("name")#! </cfoutput>
</cfif>
</strong>
</div>

<div class="menu_nav">
<div id="nav-wrap">
<ul class="arrowunderline" id="nav">
<cfif sessionstorage.getVar("loggedin",false)>
<li class="index"><a href="index.cfm?event=contacts.index">Home</a></li>
<li><a href="index.cfm?event=Contacts.doLogout" class="fisheyeItem"><span>Logout</span></a></li>
<cfelse>
<li class="edit"><a href="index.cfm?event=contacts.login">Connexion</a></li>
</cfif>
</ul>
</div>
</div>
<!--// Navigation End //-->
</div>
<!--// -head right end //-->
</div>
<!--// logo bg end //-->
</div>
<div class="main">
<!--header end -->
<!--- Render The View. This is set wherever you want to render the view in your Layout. --->
<cfoutput>#renderView()#</cfoutput>
</div>
<footer>
Copyright User
</footer>
</body>
</html>

6- Views

Nos différentes vues:

Editor.cfm

<cfoutput>
<h1>Contact Editor</h1>

#getPlugin("MessageBox").renderit()#
#html.startForm(action="contacts.save")#
#html.entityFields(entity=prc.contacts,fieldwrapper="div")#
#html.submitButton()# or #html.href(href="contacts",text="Cancel")#
#html.endForm()#
</cfoutput>

index.cfm

<cfoutput>
<h1>Contacts</h1>
#getPlugin("MessageBox").renderit()#
#html.href(href='contacts.editor',text="Create Contact")#
<br><br>


<cfloop array="#prc.contacts#" index="contact">
<div class="contact">
Nom: #contact.getName()#<br/>
Password : #contact.getPassword()# <br/>
(Mail :#contact.getEmail()#) <br/>
#html.href(href='contacts.editor&contactID=#contact.getContactID()#',text="[ Edit ]")#
#html.href(href='contacts.delete&contactID=#contact.getContactID()#',text="[ Delete ]",onclick="return confirm('Really Delete?')")#
<hr>
</div>
</cfloop>

</cfoutput>

login.cfm

<cfoutput>
<div id="loginForm">
<h1>Espace de connexion</h1>
<p>Veuillez entrer votre nom et mot de passe</p>

<!--- display any messages in the event --->
#getPlugin("MessageBox").renderit()#

<form name="loginForm" method="POST" action="#event.buildLink('contacts.doLogin')#">
<p>Username:<br/>
<input type="text" name="name">
<p>Password:<br/>

<input type="password" name="password">
<p>
<input type="submit" value="Login" name="submit">
</form>

</div>
</cfoutput>

7-Model

Contact.cfc, notre objet

/**
* A cool Contact entity
*/
component persistent="true" table="newcontacts" {

// Primary Key
property name="contactID" fieldtype="id" column="id" generator="native" setter="false";

// Properties
property name="name" ormtype="string";

property name="email" ormtype="string";

property name="password" ormtype="string";
// DEPENDENCIES via WireBOX



// validation
this.constraints = {
name = {required=true},

email = {required=true, type="email"},

password = {required=true, type="password"}
};

}

datastore.cfc qui contient les fonctions de validation de l’admin

<cfcomponent name="datastore" hint="A security datastore object">

<!------------------------------------------- CONSTRUCTOR ------------------------------------------->

<cfset variables.instance = structnew()>
<cfset variables.instance.datastoreFile = Expandpath("model/users.xml.cfm")>
<cfif server.ColdFusion.ProductName eq "Coldfusion Server">
<cfset variables.instance.qUsers = queryNew("id,name,password,name","varchar,varchar,varchar,varchar")>
<cfelse>
<cfset variables.instance.qUsers = queryNew("id,name,password,name")>
</cfif>
<cfset variables.instance.hashType = "SHA">

<cffunction name="init" access="public" returntype="datastore" output="false">
<cfset parseXML()>
<cfreturn this>
</cffunction>

<!------------------------------------------- PUBLIC ------------------------------------------->

<cffunction name="validateUser" access="public" returntype="struct" output="false">
<cfargument name="name" type="string" required="true">
<cfargument name="password" type="string" required="true">
<cfset var rtnStruct = structnew()>
<cfset var thePassword = "">
<cfset rtnStruct.validated = false>
<cfset rtnStruct.qUser = "">

<!--- BD --->
<cfif server.ColdFusion.ProductName eq "Coldfusion Server">
<cfset thePassword = hash(arguments.password,'SHA')>
<cfelse>
<cfset thePassword = hash(arguments.password,'SHA')>
</cfif>
<!--- Validate a user --->
<cfquery name="rtnStruct.qUser" dbtype="query">
select *
from instance.qUsers
where name= <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#"> and
password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#thePassword#">
</cfquery>
<cfif rtnStruct.qUser.recordcount>
<cfset rtnStruct.validated = true>
</cfif>
<cfreturn rtnStruct>
</cffunction>

<cffunction name="getUsers" access="public" returntype="query" output="false">
<cfreturn instance.qUsers>
</cffunction>

<cffunction name="getInstance" access="public" returntype="struct" output="false">
<cfreturn instance>
</cffunction>

<!------------------------------------------- PRIVATE METHODS ------------------------------------------->

<cffunction name="parseXML" access="private" returntype="any" output="false">
<cfset var FileContents = "">
<cfset var aUsers = ArrayNew(1)>
<cfset var i = 0>
<cffile action="read" file="#instance.datastoreFile#" variable="FileContents">
<cfset aUsers = XMLParse(FileContents).xmlroot.XMLChildren>

<cfloop from="1" to="#arrayLen(aUsers)#" index="i">
<cfset QueryAddRow(instance.qUsers,1)>
<cfset QuerySetCell(instance.qUsers,"id", trim(aUsers[i].XMLAttributes["id"]))>
<cfset QuerySetCell(instance.qUsers,"name", trim(aUsers[i].XMLAttributes["name"]))>
<cfset QuerySetCell(instance.qUsers,"password", trim(aUsers[i].XMLAttributes["password"]))>
<cfset QuerySetCell(instance.qUsers,"name", trim(aUsers[i].XMLAttributes["name"]))>
</cfloop>
</cffunction>

</cfcomponent>

securityInterceptor.cfc qui nous permet de bloquer l’appli si non connecté

component {

public void function preProcess (event,rc,prc) {

var loggingIn = false;
var oSession = getPlugin("SessionStorage");
//si on est de le login
if ( event.getCurrentEvent() eq "contacts.doLogin" )
loggingIn = true;


//S'il n'y a pas de session on redirige vers la page de connexion
if ( (not oSession.exists("loggedin") or not oSession.getVar("loggedin") ) and not loggingIn ){
//Override the incoming event.
Event.overrideEvent("contacts.Login");
getPlugin("MessageBox").setMessage("warning", "Interceptor: Veuillez vous connecter");
}
}

}

 

users.xml.cfm dans lequel on définit les admin par défaut

<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="469480AB-93E2-E53C-4D09BA5728275354" name="lmajano" password="F67B80E14E470066969BA913D4309CD91DE6051D" />
<user id="46A6965F-C9D7-1FCA-36689D8B808641B1" name="admin" password="D033E22AE348AEB5660FC2140AEC35850C4DA997" />
</users>

Voila, on a l’ensemble de nos fichiers pour une application Coldfusion qui marche

Ici j’ai volontairement mélangé du code pour montrer la variété des possibilités avec ce langage.(du code issu des tutos coldbox pour le crud, et du code issu des samples de coldbox comme simple blog, samplelogginapp etc)

Une partie ORM pour le CRUD, qui fonctionne avec des objets, et une partie normale, pour l’admin dont les informations sont contenues en dur dans un fichier xml, avec un fichier de fonctions ou requetes sql pour aller chercher l’info.